Effective C++, 2E | Item 45: Know what functions C++ silently writes and calls Back to Miscellany Continue to Item 46: Prefer compile-time and link-time errors to runtime errors. Item 45: Know what functions C++ silently writes and calls. When is an empty class not an empty class? When C++ gets through with it. If you don't declare them yourself, your thoughtful compilers will declare their own versions of a copy constructor, an assignment operator, a destructor, and a pair of address-of operators. Furthermore, if you don't declare any constructors, they will declare a default constructor for you, too. All these functions will be public. In other words, if you write this, class Empty{}; it's the same as if you'd written this: class Empty { public: Empty(); // default constructor Empty(const Empty& rhs); // copy constructor ~Empty(); // destructor see // below for whether // it's virtual Empty& operator=(const Empty& rhs); // assignment operator Empty* operator&(); // address-of operators const Empty* operator&() const; }; Now these functions are generated only if they are needed, but it doesn't take much to need them. The following code will cause each function to be generated: const Empty e1; // default constructor; // destructor Empty e2(e1); // copy constructor e2 = e1; // assignment operator Empty *pe2 = &e2; // address-of // operator (non-const) const Empty *pe1 = &e1; // address-of // operator (const) Given that compilers are writing functions for you, what do the functions do? Well, the default constructor and the destructor don't really do anything. They just enable you to create and destroy objects of the class. (They also provide a convenient place for implementers to place code whose execution takes care of "behind the scenes" behavior see Items 33 and M24.) Note that the generated destructor is nonvirtual (see Item 14) unless it's for a class inheriting from a base class that itself declares a virtual destructor. The default address-of operators just return the address of the object. These functions are effectively defined like this: inline Empty::Empty() {} inline Empty::~Empty() {} inline Empty * Empty::operator&() { return this; } inline const Empty * Empty::operator&() const { return this; } As for the copy constructor and the assignment operator, the official rule is this: the default copy constructor (assignment operator) performs memberwise copy construction (assignment) of the nonstatic data members of the class. That is, if m is a nonstatic data member of type T in a class C and C declares no copy constructor (assignment operator), m will be copy constructed (assigned) using the copy constructor (assignment operator) defined for T, if there is one. If there isn't, this rule will be recursively applied to m's data members until a copy constructor (assignment operator) or built-in type (e.g., int, double, pointer, etc.) is found. By default, objects of built-in types are copy constructed (assigned) using bitwise copy from the source object to the destination object. For classes that inherit from other classes, this rule is applied to each level of the inheritance hierarchy, so user-defined copy constructors and assignment operators are called at whatever level they are declared. I hope that's crystal clear. But just in case it's not, here's an example. Consider the definition of a NamedObject template, whose instances are classes allowing you to associate names with objects: template class NamedObject { public: NamedObject(const char *name, const T& value); NamedObject(const string& name, const T& value); ... private: string nameValue; T objectValue; }; Because the NamedObject classes declare at least one constructor, compilers won't generate default constructors, but because the classes fail to declare copy constructors or assignment operators, compilers will generate those functions (if they are needed). Consider the following call to a copy constructor: NamedObject no1("Smallest Prime Number", 2); NamedObject no2(no1); // calls copy constructor The copy constructor generated by your compilers must initialize no2.nameValue and no2.objectValue using no1.nameValue and no1.objectValue, respectively. The type of nameValue is string, and string has a copy constructor (which you can verify by examining string in the standard library see Item 49), so no2.nameValue will be initialized by calling the string copy constructor with no1.nameValue as its argument. On the other hand, the type of NamedObject::objectValue is int (because T is int for this template instantiation), and no copy constructor is defined for ints, so no2.objectValue will be initialized by copying the bits over from no1.objectValue. The compiler-generated assignment operator for NamedObject would behave the same way, but in general, compiler-generated assignment operators behave as I've described only when the resulting code is both legal and has a reasonable chance of making sense. If either of these tests fails, compilers will refuse to generate an operator= for your class, and you'll receive some lovely diagnostic during compilation. For example, suppose NamedObject were defined like this, where nameValue is a reference to a string and objectValue is a const T: template class NamedObject { public: // this ctor no longer takes a const name, because name- // Value is now a reference-to-non-const string. The char* // ctor is gone, because we must have a string to refer to NamedObject(string& name, const T& value); ... // as above, assume no // operator= is declared private: string& nameValue; // this is now a reference const T objectValue; // this is now const }; Now consider what should happen here: string newDog("Persephone"); string oldDog("Satch"); NamedObject p(newDog, 2); // as I write this, our dog // Persephone is about to // have her second birthday NamedObject s(oldDog, 29); // the family dog Satch // (from my childhood) // would be 29 if she were // still alive p = s; // what should happen to // the data members in p? Before the assignment, p.nameValue refers to some string object and s.nameValue also refers to a string, though not the same one. How should the assignment affect p.nameValue? After the assignment, should p.nameValue refer to the string referred to by s.nameValue, i.e., should the reference itself be modified? If so, that breaks new ground, because C++ doesn't provide a way to make a reference refer to a different object (see Item M1). Alternatively, should the string object to which p.nameValue refers be modified, thus affecting other objects that hold pointers or references to that string, i.e., objects not directly involved in the assignment? Is that what the compiler-generated assignment operator should do? Faced with such a conundrum, C++ refuses to compile the code. If you want to support assignment in a class containing a reference member, you must define the assignment operator yourself. Compilers behave similarly for classes containing const members (such as objectValue in the modified class above); it's not legal to modify const members, so compilers are unsure how to treat them during an implicitly generated assignment function. Finally, compilers refuse to generate assignment operators for derived classes that inherit from base classes declaring the standard assignment operator private. After all, compiler-generated assignment operators for derived classes are supposed to handle base class parts, too (see Items 16 and M33), but in doing so, they certainly shouldn't invoke member functions the derived class has no right to call. All this talk of compiler-generated functions gives rise to the question, what do you do if you want to disallow use of those functions? That is, what if you deliberately don't declare, for example, an operator= because you never ever want to allow assignment of objects in your class? The solution to that little teaser is the subject of Item 27. For a discussion of the often-overlooked interactions between pointer members and compiler-generated copy constructors and assignment operators, check out Item 11. Back to Miscellany Continue to Item 46: Prefer compile-time and link-time errors to runtime errors.