Make default operations consistent
Reason
The default operations are conceptually a matched set. Their semantics are interrelated. Users will be surprised if copy/move construction and copy/move assignment do logically different things. Users will be surprised if constructors and destructors do not provide a consistent view of resource management. Users will be surprised if copy and move don't reflect the way constructors and destructors work.
Example, bad
class Silly { // BAD: Inconsistent copy operations
class Impl {
// ...
};
shared_ptr<Impl> p;
public:
Silly(const Silly& a) : p(make_shared<Impl>()) { *p = *a.p; } // deep copy
Silly& operator=(const Silly& a) { p = a.p; return *this; } // shallow copy
// ...
};
These operations disagree about copy semantics. This will lead to confusion and bugs.
Enforcement
- (Complex) A copy/move constructor and the corresponding copy/move assignment operator should write to the same data members at the same level of dereference.
- (Complex) Any data members written in a copy/move constructor should also be initialized by all other constructors.
- (Complex) If a copy/move constructor performs a deep copy of a data member, then the destructor should modify the data member.
- (Complex) If a destructor is modifying a data member, that data member should be written in any copy/move constructors or assignment operators.
C.dtor: Destructors
"Does this class need a destructor?" is a surprisingly insightful design question. For most classes the answer is "no" either because the class holds no resources or because destruction is handled by the rule of zero; that is, its members can take care of themselves as concerns destruction. If the answer is "yes", much of the design of the class follows (see the rule of five).