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.