Effective C++, 2E | Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory Back to Constructors, Destructors, and Assignment Operators Continue to Item 12: Prefer initialization to assignment in constructors. 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 consequences. If you make these object definitions, String a("Hello"); String b("World"); the situation is as shown below: 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 assignment, 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 below. 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 example: 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 isalready 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 destroyed. 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 anyway: 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 deleted. 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 scope. 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 speed. 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. Back to Constructors, Destructors, and Assignment Operators Continue to Item 12: Prefer initialization to assignment in constructors.