Can this Assignment and Copy cnstr be organized better - c++

I am trying to improve my understanding of the copy constructor and copy assign. operator
Here is a simple class that I came up with
class Bar
{
char* name;
int zip;
std::string address;
public:
Bar(const Bar& that)
{
//Copy constructor
size_t len = strlen(that.name + 1);
name = new char[len];
strcpy(name, that.name);
//Copy the zip
this->zip = that.zip;
//Copy the address
this->address = that.address;
}
Bar& operator=(const Bar& that)
{
//Assignment operator
if(this != &that)
{
//Copy the name
size_t len = strlen(that.name + 1);
name = new char[len];
strcpy(name, that.name);
//Copy the zip
this->zip = that.zip;
//Copy the address
this->address = that.address;
}
return *this;
}
};
My question is since the code in the copy constructor and copy assignment operator are the same does it make more sense to unify that into a deep copy method so that incase I add another member variable I dont have to add another line to the copy cnstr and copy assign. section ? Any suggestions ?

The "normal" way of doing things where you manage your own resources is a little different:
char* cppstrdup(const char*s, int len=0);
class Bar
{
char* name;
int zip;
std::string address;
public:
Bar(const Bar& that)
:name(nullptr),
zip(that->zip),
address(that->address)
{
name = cppstrdup(that.name); //done here for exception safety reasons
}
Bar(Bar&& that) //if you have C++11 then you'll want this too
:name(nullptr)
{
swap(*this,that);
}
~Bar() //you forgot the destructor
{
delete [] name;
}
Bar& operator=(Bar that) //this is called copy and swap.
{ //"that" is a copy (notice, no & above), and we simply swap
swap(*this,that);
return *this;
}
friend void swap(Bar& left, Bar& right)
{
using std::swap;
swap(left.name, right.name);
swap(left.zip, right.zip);
swap(left.address, right.address);
}
};
//uses new instead of malloc
inline char* cppstrdup(const char* s, int len)
{
if (s==0) return nullptr;
if (len==0) len = strlen(s);
char* r = new char[len+1];
strncpy(r, len+1, s);
r[len] = 0;
return r;
}
The benefits of this pattern is that it is much easier to get exception safety, often with the strong exception guarantee.
Of course, even more normal is to not use char* name, and obey the "Rule of Zero" isntead. In which case, it becomes VERY different:
class Bar
{
std::string name;
int zip;
std::string address;
public:
Bar() = default; //well, that's easy
};

Check COPY & SWAP idiom. In short - your logic goes into copy constructor and swap method and your assignment operator looks like:
Bar& operator=(const Bar& that)
{
Bar temp(that);
swap(*this, temp);
return *this;
}

Related

Syntax and overloading copy constructor

I am new to the topic of overloading copy constructors and I just wanted someone to look at my code for my class and see if I am overloading my copy constructor correctly. It is only using a single string as user input. Also, do I need the '&' or not?
class TODO {
private:
string entry;
public:
List* listArray = nullptr;
int itemCount = 0, currInvItem = 0;
int maxLength = 22;
TODO() { entry = ""; };
TODO(const string& ent) { setEntry(ent); }; // Is this correct?
void setEntry(string ent) { entry = ent; };
string getEntry() const { return entry; };
void greeting();
void programMenu();
void newArray();
void getList();
void incList();
void delTask();
string timeID();
string SystemDate();
friend istream& operator >>(istream& in, TODO& inv);
friend ostream& operator <<(ostream& out, TODO& inv);
void componentTest();
void setTask(string a);
string getTask();
bool validTask(string a);
bool notEmpty(string e);
};
That's correct, but it's just a constructor of TODO taking a const reference to a string. You can test it here.
Passing const string& ent (by const reference) is not a bad idea. Another option would be to pass string ent (by value), and move that string when initializing entry, i.e. : entry{ std::move(ent) }. As here.
The class TODO has a default copy constructor. Check the line at the Insight window here (you'll have to click Play first).:
// inline TODO(const TODO &) noexcept(false) = default;
The default copy constructor would just copy the members, including the List pointer, but not the List elements (shallow copy). Notice both instances of TODO would be pointing to the same List elements, and any operation on them would affect both instances.
Should you want to implement a custom copy constructor, the syntax would be (see here):
TODO(const TODO& other) {
std::cout << "custom copy_ctor\n";
*this = other;
// Copy listArray elements
...
}

Inserting new Object to Repository throws error

I've created an object and a Repository for it.
When I try inserting the object into the Repository (with the insert function i've created) I get compile error.
The Class I'm trying to insert into Repository
class Payment{
private:
int day;
int amount;
char *type;
public:
Payment();
Payment(int day, int amount, char *type);
Payment(const Payment &p);
~Payment();
//getters
int getDay()const;
int getAmount()const;
char* getType()const;
//setters
void setDay(int day);
void setAmount(int amount);
void setType(char* type);
//operator
Payment& operator=(const Payment& other);
friend ostream& operator<<(ostream &os,const Payment &obj);
};
//copy constructor
Payment::Payment(const Payment & p){
this->day = p.day;
this->amount = p.amount;
if(this->type!=NULL)
delete[] this->type;
this->type = new char[strlen(p.type)+1];
strcpy_s(this->type, strlen(p.type) + 1, p.type);
}
//assignment operator
Payment& Payment::operator=(const Payment &other) {
this->day = other.day;
this->amount = other.amount;
this->type = new char[strlen(other.type) + 1];
strcpy_s(this->type, strlen(other.type) + 1, other.type);
return *this;
}
//destructor
Payment::~Payment(){
this->day = 0;
this->amount = 0;
if (this->type != NULL) {
delete[]this -> type;
this->type = NULL;
}
}
//Repository header
class Repository{
private:
vector<Payment> list;
public:
Repository();
int getLength();
void insert(const Payment& obj);
void remove(int position);
};
//Repository cpp
Repository::Repository(){
this->list.reserve(10);
}
//return the size of the list
int Repository::getLength() {
return this->list.size();
}
//add payment to list
void Repository::insert(const Payment &obj) {
this->list.emplace_back(obj);
}
//remove payment from list
void Repository::remove(int position) {
this->list.erase(this->list.begin() + position);
}
In main function I have
char c[] = "some characters";
Payment pay = Payment(7,9,c);
Repository rep = Repository();
rep.insert(pay);
When I run the program I get the error "
Expression: _CrtlsValidHeapPointer(block) "
Since std::vector will make copies, a std::vector<Payment> requires that Payment has correct copy semantics. Your copy constructor and assignment operator are not implemented correctly. The assignment operator causes memory leaks, since you failed to delete [] the existing memory.
The easiest solution is to drop using the char *type; member and simply use std::string type;. Then the Payment class would have the correct copy semantics automatically.
Given that, the corrections to your Payment class is below:
#include <algorithm>
//...
Payment::Payment() : day(0), amount(0), type(nullptr) {}
Payment::Payment(const Payment & p) : day(p.day), amount(p.amount), type(nullptr)
{
if ( p.type )
{
type = new char[strlen(p.type) + 1];
strcpy_s(this->type, strlen(p.type) + 1, p.type);
}
}
// Use the copy/swap idiom
Payment& Payment::operator=(const Payment &other)
{
Payment temp(other); // make a temporary copy
// swap out contents of temporary with this object
std::swap(temp.day, day);
std::swap(temp.amount, amount);
std::swap(temp.type, type);
return *this;
} // when this brace has been reached, the temp copy dies off with the old data
Payment::~Payment()
{
delete [] type;
}
The above uses the copy/swap idiom within the assignment operator. The copy constructor uses a member initialization list.
The destructor need not check for a null pointer, since deleting a nullptr is perfectly valid.
Now adding to a std::vector is working well, without any runtime error (using the code #PaulMcKenzie posted). I've also found an example of code that's working, where only the assignment operator is kinda different.
Converted to my code would be (and it's working either):
Payment& Payment::operator=(const Payment &other) {
if (this != &other) {
this->setDay(other.day);
this->setAmount(other.amount);
this->setType(other.type);
}
return *this;
}
Thanks for the help! Now it's working perfectly! I didn't get to study very much from <algorithm> library, so I'll have to take a closer look. Wish you the best luck! ^_^

Segmentation fault with accessor function in C++

I am working on a homework assignment for a C++ class, and having some difficulties. My program is segfaulting due to "use of uninitialised value of size 8" and "invalid read of size 8" in item::name() which is called by item::operator=.
#ifndef ITEM_H
#define ITEM_H
const double WEIGHT_DEFAULT = 1.0;
const int ITEM_NAME_LENGTH = 30;
class item {
public:
// Constructors and deconstructor
item();
item(char* nme, double weight);
virtual ~item();
// Copy constructor
item(const item& itm);
// Overload assignment operator
const item& operator=(const item& itm);
// Overload inequality operator
bool operator!=(const item&) const;
// Mutators and accessors
const char* name(void) const;
double weight(void) const;
void weight(double wght);
void name(char* nme);
protected:
private:
char* m_name;
double m_weight;
};
#endif // ITEM_H
Here is the portion of the implementation that I think is having issues:
...
const item& item::operator=(const item& itm)
{
if (strcmp(this->name(), itm.name()) != 0)
{
this->weight(itm.weight());
strcpy(m_name, itm.name());
}
return *this;
}
const char* item::name(void) const {
return m_name;
}
...
As you can see, part of the assignment is to use c-strings, thus I'm stuck with the messy details. My understanding of pointers and classes is that when working with both, we need to implement destructors, copy constructors, and overload the assignment operator. I've done all of the above. I looked at some info on the copy-and-swap idiom here on Stack Overflow, and tried implementing that, but couldn't get that to work with my code either.
My brain is getting frazzled trying to puzzle out where I went wrong. Could someone please tell me what I'm missing?
Thanks!
EDIT
Here are the constructors, destructor, and such:
item::item() {
//ctor
m_name = new char[ITEM_NAME_LENGTH];
strncpy(m_name, " ", ITEM_NAME_LENGTH - 1);
this->weight(WEIGHT_DEFAULT);
return;
}
item::item(char* nme, double wght) {
m_name = new char[ITEM_NAME_LENGTH];
strncpy(m_name, nme, ITEM_NAME_LENGTH - 1);
// We want to check for a rational value being
// passed in to weight. In theory, an item has
// to have *some* kind of weight, so we check
// for a weight of 0. If a weight of 0 has been passed in,
// we instead initialize the item's weight to the
// default weight (set in the header file).
if (wght > 0.0) {
this->weight(wght);
} else {
this->weight(WEIGHT_DEFAULT);
}
return;
}
item::~item() {
//dtor
// TODO: We need to clean up any variables here.
delete[] m_name;
}
item::item(const item& itm) {
// copy ctor
this->weight(itm.weight());
strncpy(m_name, itm.name(), ITEM_NAME_LENGTH - 1);
}
const item& item::operator=(const item& itm)
{
if (strcmp(this->name(), itm.name()) != 0)
{
this->weight(itm.weight());
strcpy(m_name, itm.name());
}
return *this;
}
bool item::operator!=(const item& itm) const
{
return (strcmp(m_name, itm.name()) != 0);
}
EDIT 2
Now that I've stopped trying to dynamically allocate the name c-string, I'm getting memory errors on the weight function...
double item::weight(void) const {
return m_weight;
}
void item::weight(double wght)
{
m_weight = wght;
}
First off, you don't need to explicitly return; from void statements.
Second:
When creating a copy constructor, you need to allocate the space that will be used to hold the memory of that constructor, the same way you do for the regular constructor.
Try this in your copy constructor:
m_name = new char[ITEM_NAME_LENGTH];
Edit: for completeness this is what my "appears to be working" code is:
#include <stdio.h>
#include <string.h>
const double WEIGHT_DEFAULT = 1.0;
const int ITEM_NAME_LENGTH = 30;
class item {
public:
// Constructors and deconstructor
item();
item(char* nme, double weight);
virtual ~item();
// Copy constructor
item(const item& itm);
// Overload assignment operator
const item& operator=(const item& itm);
// Overload inequality operator
bool operator!=(const item&) const;
// Mutators and accessors
const char* name(void) const;
double weight(void) const;
void weight(double wght);
void name(char* nme);
protected:
private:
char* m_name;
double m_weight;
};
const item& item::operator=(const item& itm)
{
if (strcmp(this->name(), itm.name()) != 0)
{
m_name = new char[ITEM_NAME_LENGTH];
this->weight(itm.weight());
strcpy(m_name, itm.name());
}
return *this;
}
const char* item::name(void) const {
return m_name;
}
double item::weight(void) const {
return m_weight;
}
void item::weight(double wght) {
m_weight = wght;
}
item::item() {
//ctor
m_name = new char[ITEM_NAME_LENGTH];
strncpy(m_name, " ", ITEM_NAME_LENGTH - 1);
this->weight(WEIGHT_DEFAULT);
return;
}
item::item(char* nme, double wght) {
m_name = new char[ITEM_NAME_LENGTH];
strncpy(m_name, nme, ITEM_NAME_LENGTH - 1);
// We want to check for a rational value being
// passed in to weight. In theory, an item has
// to have *some* kind of weight, so we check
// for a weight of 0. If a weight of 0 has been passed in,
// we instead initialize the item's weight to the
// default weight (set in the header file).
if (wght > 0.0) {
this->weight(wght);
} else {
this->weight(WEIGHT_DEFAULT);
}
return;
}
item::~item() {
//dtor
// TODO: We need to clean up any variables here.
delete[] m_name;
}
item::item(const item& itm) {
// copy ctor
this->weight(itm.weight());
m_name = new char[ITEM_NAME_LENGTH];
strncpy(m_name, itm.name(), ITEM_NAME_LENGTH - 1);
}
bool item::operator!=(const item& itm) const
{
return (strcmp(m_name, itm.name()) != 0);
}
int main(int argc, char* argv[])
{
item i("test",2.0);
item b = i;
item c;
item d = c;
return 0;
}
I guess using std::string is out as part of the exercise, right? Otherwise, that's the way to go for string handling. Another thing you could try is to avoid the dynamic allocation of the string. Instead, just use a char m_name[ITEM_NAME_LENGTH];. BTW: Which length is that? If it's the maximum number of characters in the name, you need a +1 for the terminating NUL.
That said, one more thing: In assignment operators, you compare if(this != &other), i.e. you compare the address of the two objects. The point is to avoid self-assignment, which is error-prone. Your assignment checks if the names are different and only then copies the names and the weight. What if only the weight was different?

How to call copy constructor from inside operator constructor correctly?

My code can also be found here.
This code would work (but there is much code duplication):
Employee::Employee(const Employee& x)
{
name=new char [strlen(x.name)+1];
strcpy(name, x. name);
strcpy(EGN, x.EGN);
salary=x.salary;
}
void Employee::operator=(const Employee& x)
{
delete[] name;
name=new char [strlen(x.name)+1];
strcpy(name, x. name);
strcpy(EGN, x.EGN);
salary=x.salary;
}
Employee::Employee(char* n, char* e, double s)
{
name = new char [strlen(n)+1];
strcpy(name, n);
strcpy(EGN, e);
salary=s;
}
Below is my attempt to avoid writing same thing three times... but it does not work. Isn't it possible to make that code shorter?
Employee::Employee(char* n, char* e, double s)
{
name = new char [strlen(n)+1];
strcpy(name, n);
strcpy(EGN, e);
salary=s;
}
Employee::Employee(const Employee& x)
{
Employee(x.name, x.EGN, x.salary);
}
void Employee::operator=(const Employee& x)
{
delete[] name;
Employee(x);
}
What you are attempting is not allowed by the language. However, C++11 allows delegating constructors, so you could do something like this:
Employee::Employee(const Employee& x) : Employee(x.name, x.EGN, x.salary){}
Note that one constructor is called in the initialization list of the other.
Before C++11, an option would have been to have some kinf of initialization function called from all the constructors. However, this is semantically different because the function call performs assignment to member variables, not initialization.
You cannot call a constructor as a member function. You can create a member function, and call it from the constructor, and from all the other places.
void Employee::Init(const char* n, const char* e, double s)
{
name = new char [strlen(n)+1];
strcpy(name, n);
strcpy(EGN, e);
salary=s;
}
void Employee::Init(const Employee &x)
{
Init(x.name, x.EGN, x.salary);
}
Employee::Employee(char* n, char* e, double s)
{
Init(n,e,s);
}
Employee::Employee(const Employee& x)
{
Init(x);
}
void Employee::operator=(const Employee& x)
{
delete[] name;
Init(x);
}
Try this in C++11
Employee::Employee(char* n, char* e, double s)
{
name = new char [strlen(n)+1];
strcpy(name, n);
strcpy(EGN, e);
salary=s;
}
// Constructor chaining (delegation) like this.
Employee::Employee(const Employee& x)
: Employee(x.name, x.EGN, x.salary)
{}
// Use copy and swap for the assignment operator:
// Common convention to return a reference to yourself to allow chaingin.
Employee& Employee::operator=(Employee x) // Pass by value to get the copy.
{
x.swap(*this); // Then swap members
return *this;
} // Destructor will then cleanup.

std::map and _CrtIsValidHeapPointer error

i try to use the std::map with
class DEMO {
public:
DEMO();
virtual ~DEMO();
DEMO &operator =(const DEMO &d);
DEMO(const DEMO& d);
BYTE* Arr() const;
private:
BYTE *m_array;
};
DEMO &DEMO::operator =(const DEMO &d) {
memcpy(m_array, d.Arr(), 1);
return *this;
}
DEMO::DEMO(const DEMO& d) {
//call operator=
*this = d;
}
const BYTE* DEMO::Arr() const {
return m_array;
}
DEMO::DEMO() {
m_array = new BYTE[1];
}
DEMO::~DEMO() {
if (m_array != 0)
delete [] m_array;
}
class MyClass {
private:
typedef map<unsigned int,DEMO> t_mapType;
t_mapType m_map;
void Test();
}
void MyClass::Test() {
DEMO myDEMO;
m_map[1] = myDEMO;
}
if i call Test() within the class, i get the error assert _CrtIsValidHeapPointer...
i checked with breakpoints and i see, during assingment (m_map[1] = myDEMO;) the destructor of DEMO gets called and i get the error on delete [] m_array; - how i make this running ?
The copy constructor and assignment operators are wrong. You need to allocate memory for m_array before doing memcpy. When you try to insert a DEMO object into the map using m_map[1] = myDEMO; a copy of the myDEMO is being created. To create a copy the DEMO class's copy ctor is used. Since the memory for m_array is not allocated while doing this copy, the memcpy call is crashing.
It's almost always wrong to base the copy ctor implementation on the assignment operator's. An assignment operator has to tear down the object's old state and build a new state. The copy ctor only has to build a new state.
If you want to do it this way, your copy ctor first needs to build a default state for the assignment operator to tear down. Of course, that's wasting resources.
That said, actually you should use the copy-and-swap idiom to base your assignment operator on your dtor and copy ctor instead. See here for a more thorough explanation of the above.
Use a std::vector to manage your BYTE*.
class DEMO {
public:
DEMO();
virtual ~DEMO();
//DEMO &operator =(const DEMO &d);
//DEMO(const DEMO& d);
const std::vector<BYTE>& Arr() const;
private:
std::vector<BYTE> m_array;
};
// Doesn't even need to be implemented by us anymore
//DEMO &DEMO::operator =(const DEMO &d) {
// memcpy(m_array, d.Arr(), 1);
// return *this;
//}
// Again, doesn't need to be implemented
//DEMO::DEMO(const DEMO& d) {
// //call operator=
// *this = d;
//}
// Just changed to return const vector&.
const std::vector<BYTE>& DEMO::Arr() const {
return m_array;
}
// Changed to use initializer list
DEMO::DEMO()
: m_array(1) {
// Old code: m_array = new BYTE[1];
}
// Doesn't need any code by us
DEMO::~DEMO() {
//if (m_array != 0)
// delete [] m_array;
}
i changed my code to use pointer to object instead of object for the value part in the map:
typedef map<unsigned int,DEMO*> t_mapType;
so i can assing as follow:
m_map[1] = new DEMO();
and i have to use a own free-mem method:
void DEMO::Clear() {
if (m_map.size() > 0) {
for (t_mapType::const_iterator it = m_map.begin(); it != m_mp.end(); ++it) {
if (it->second != 0)
delete it->second;
}
m_map.clear();
}
}