Copy Constructor and = Operator Overload in C++: Is a Common Function Possible

Copy constructor and = operator overload in C++: is a common function possible?

Yes. There are two common options. One - which is generally discouraged - is to call the operator= from the copy constructor explicitly:

MyClass(const MyClass& other)
{
operator=(other);
}

However, providing a good operator= is a challenge when it comes to dealing with the old state and issues arising from self assignment. Also, all members and bases get default initialized first even if they are to be assigned to from other. This may not even be valid for all members and bases and even where it is valid it is semantically redundant and may be practically expensive.

An increasingly popular solution is to implement operator= using the copy constructor and a swap method.

MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}

or even:

MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}

A swap function is typically simple to write as it just swaps the ownership of the internals and doesn't have to clean up existing state or allocate new resources.

Advantages of the copy and swap idiom is that it is automatically self-assignment safe and - providing that the swap operation is no-throw - is also strongly exception safe.

To be strongly exception safe, a 'hand' written assignment operator typically has to allocate a copy of the new resources before de-allocating the assignee's old resources so that if an exception occurs allocating the new resources, the old state can still be returned to. All this comes for free with copy-and-swap but is typically more complex, and hence error prone, to do from scratch.

The one thing to be careful of is to make sure that the swap method is a true swap, and not the default std::swap which uses the copy constructor and assignment operator itself.

Typically a memberwise swap is used. std::swap works and is 'no-throw' guaranteed with all basic types and pointer types. Most smart pointers can also be swapped with a no-throw guarantee.

Using copy constructor in assignment operator

Here is the copy/swap idiom on your example in a nutshell:

#include <algorithm>

class Obj
{
int *p;
void swap(Obj& left, Obj& right);

public:
Obj(int x = 0) : p(new int(x)) {}
Obj(const Obj& s);
Obj& operator = (const Obj& s);
~Obj() { delete p; }
};

Obj::Obj(const Obj& source) : p(new int(*source.p))
{}

void Obj::swap(Obj& left, Obj& right)
{
std::swap(left.p, right.p);
}

Obj & Obj::operator=(const Obj & source)
{
Obj temp(source);
swap(*this, temp);
return *this;
}

int main()
{
Obj o1(5);
Obj o2(o1);
Obj o3(10);
o1 = o3;
}

To see how it works, I purposefully created a member that is a pointer to dynamically allocated memory (this would be problematic if there were no user-defined copy constructor and assignment operator).

If you focus on the assignment operator, it calls the Obj copy constructor to construct a temporary object. Then the Obj-specific swap is called that swaps the individual members. Now, the magic is in the temp object after the swap is called.

When the destructor of temp is called, it will call delete on the pointer value that this used to have, but was swapped out with the temp pointer. So when temp goes out of scope, it cleaned up the memory allocated by the "old" pointer.

Also, note that during assignment, if new throws an exception during the creation of the temporary object, the assignment will throw an exception before any members of this become changed. This prevents the object from having members that may be corrupted due to having them changed inadvertently.

Now, a previous answer was given that uses the often-used "shared code" approach to copy assignment. Here is a full example of this method, and an explanation of why it has issues:

class Obj
{
int *p;
void CopyMe(const Obj& source);

public:
Obj(int x = 0) : p(new int(x)) {}
Obj(const Obj& s);
Obj& operator = (const Obj& s);
~Obj() { delete p; }
};

void Obj::CopyMe(const Obj& source)
{
delete p;
p = new int(*source.p);
}

Obj::Obj(const Obj& source) : p(0)
{
CopyMe(source);
}

Obj & Obj::operator=(const Obj & source)
{
if ( this != &source )
CopyMe(source);
return *this;
}

So you would say "what's wrong with this?" Well, the thing that's wrong is that CopyMe's first thing it does is call delete p;. Then the next question you'll ask is "So what? Isn't that what we're supposed to do, delete the old memory?"

The issue with this is that there is a potential for the subsequent call to new to fail. So what we did was destroy our data before we even know that the new data will be available. If new now throws an exception, we've messed up our object.

Yes, you can fix it easily by creating a temporary pointer, allocating, and at the end, assign the temp pointer to p. But many times this can be forgotten, and code such as above remains in the codebase forever, even though it has a potential corruption bug.

To illustrate, here is the fix for the CopyMe:

void Obj::CopyMe(const Obj& source)  
{
int *pTemp = new int(*source.p);
delete p;
p = pTemp;
}

But again, you will see tons of code that use the "shared code" method that has this potential bug and not mention a word about the exception issue.

Copy constructor overloading an operator

The compiler has told you the error: constructor initializer lists are only allowed on constructor definitions.

You're implementing the copy constructor, so constructor initializer lists is not allowed, and it's only allowed on constructor while you try to use it on overloading the assignment operator. I'd recommend you to read these posts: Copy constructor and = operator overload in C++: is a common function possible? What is the copy-and-swap idiom?

MontaznaKuca::MontaznaKuca(const MontaznaKuca& kuca)
{
kvadratura = kuca.kvadratura;
bojaFasade = kuca.bojaFasade;
vlasnik = kuca.vlasnik;
postojiVrt = kuca.postojiVrt;
kuhinja = new Kuhinja;
kuhinja = kuca.kuhinja;

for (size_t i = 0; i < kuca.sobe.size(); ++i)
sobe.push_back(new Soba(*kuca.sobe[i]) );
}

MontaznaKuca& MontaznaKuca::operator=(const MontaznaKuca& kuca)
{
// copy-swap idiom is a better approach.
kvadratura = kuca.kvadratura;
bojaFasade = kuca.bojaFasade;
vlasnik = kuca.vlasnik;
postojiVrt = kuca.postojiVrt;
delete kuhinja; //deallocate old memory
kuhinja = new Kuhinja;
kuhinja = kuca.kuhinja;
// before allocating new memory you need deallocate old memory
...
for (size_t i = 0; i < kuca.sobe.size(); ++i)
sobe.push_back(new Soba(*kuca.sobe[i]) );
return *this;
}

The copy constructor and assignment operator

No, they are different operators.

The copy constructor is for creating a new object. It copies an existing object to a newly constructed object.The copy constructor is used to initialize a new instance from an old
instance. It is not necessarily called when passing variables by value into functions
or as return values out of functions.

The assignment operator is to deal with an already existing object. The assignment operator is used to change an existing instance to have
the same values as the rvalue, which means that the instance has to be
destroyed and re-initialized if it has internal dynamic memory.

Useful link :

  • Copy Constructors, Assignment Operators, and More
  • Copy constructor and = operator overload in C++: is a common function possible?

Calling assignment operator in copy constructor

Yes, that's a bad idea. All member variables of user-defined types will be initialized first, and then immediately overwritten.

That swap trick is this:

Foo& operator=(Foo rhs) // note the copying
{
rhs.swap(*this); //swap our internals with the copy of rhs
return *this;
} // rhs, now containing our old internals, will be deleted


Related Topics



Leave a reply



Submit