Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory.
Consider a class for representing String
objects:
// a poorly designed String class class String { public: String(const char *value); ~String(); ... // no copy ctor or operator= private: char *data; }; String::String(const char *value) { if (value) { data = new char[strlen(value) + 1]; strcpy(data, value); } else { data = new char[1]; *data = '\0'; } } inline String::~String() { delete [] data; }
Note that there is no assignment operator or copy constructor declared in this class. As you'll see, this has some unfortunate
If you make these object
String a("Hello"); String b("World");
the situation is as shown
Inside object a
is a pointer to memory containing the character string "Hello". Separate from that is an object b
containing a pointer to the character string "World". If you now perform an
b = a;
there is no client-defined operator=
to call, so C++ generates and calls the default assignment operator instead (see Item 45). This default assignment operator performs memberwise assignment from the members of a
to the members of b
, which for pointers (a.data
and b.data
) is just a bitwise copy. The result of this assignment is shown
There are at least two problems with this state of affairs. First, the memory that b
used to point to was never deleted; it is lost forever. This is a classic example of how a memory leak can arise. Second, both a
and b
now contain pointers to the same character string. When one of them goes out of scope, its destructor will delete the memory still pointed to by the other. For
String a("Hello"); // define and construct a
{ // open new scope String b("World"); // define and construct b
...
b = a; // execute default op=, // lose b's memory
} // close scope, call b's // destructor
String c = a; // c.data is undefined! // a.data is already deleted
The last statement in this example is a call to the copy constructor, which also isn't defined in the class, hence will be generated by C++ in the same manner as the assignment operator (again, see Item 45) and with the same behavior: bitwise copy of the underlying pointers. That leads to the same kind of problem, but without the worry of a memory leak, because the object being initialized can't yet point to any allocated memory. In the case of the code above, for example, there is no memory leak when c.data
is initialized with the value of a.data
, because c.data
doesn't yet point anywhere. However, after c
is initialized with a
, both c.data
and a.data
point to the same place, so that place will be deleted twice: once when c
is destroyed, once again when a
is
The case of the copy constructor differs a little from that of the assignment operator, however, because of the way it can bite you: pass-by-value. Of course, Item 22 demonstrates that you should only rarely pass objects by value, but consider this
void doNothing(String localString) {}
String s = "The Truth Is Out There";
doNothing(s);
Everything looks innocuous enough, but because localString
is passed by value, it must be initialized from s
via the (default) copy constructor. Hence, localString
has a copy of the pointer that is inside s
. When doNothing
finishes executing, localString
goes out of scope, and its destructor is called. The end result is by now familiar: s
contains a pointer to memory that localString
has already
By the way, the result of using delete
on a pointer that has already been deleted is undefined, so even if s
is never used again, there could well be a problem when it goes out of
The solution to these kinds of pointer aliasing problems is to write your own versions of the copy constructor and the assignment operator if you have any pointers in your class. Inside those functions, you can either copy the pointed-to data structures so that every object has its own copy, or you can implement some kind of reference-counting scheme (see Item M29) to keep track of how many objects are currently pointing to a particular data structure. The reference-counting approach is more complicated, and it calls for extra work inside the constructors and destructors, too, but in some (though by no means all) applications, it can result in significant memory savings and substantial increases in
For some classes, it's more trouble than it's worth to implement copy constructors and assignment operators, especially when you have reason to believe that your clients won't make copies or perform assignments. The examples above demonstrate that omitting the corresponding member functions reflects poor design, but what do you do if writing them isn't practical, either? Simple: you follow this Item's advice. You declare the functions (private
, as it turns out), but you don't define (i.e., implement) them at all. That prevents clients from calling them, and it prevents compilers from generating them, too. For details on this nifty trick, see Item 27.
One more thing about the String
class I used in this Item. In the constructor body, I was careful to use []
with new
both times I called it, even though in one of the places I wanted only a single object. As described in Item 5, it's essential to employ the same form in corresponding applications of new
and delete
, so I was careful to be consistent in my uses of new
. This is something you do not want to forget. Always make sure that you use []
with delete
if and only if you used []
with the corresponding use of new
.