More Books
C++ Gotchas: Avoiding Common Problems in Coding and Design
Main Page
Table of content
Copyright
Addison-Wesley Professional Computing Series
Preface
Acknowledgments
Chapter 1. Basics
Gotcha #1: Excessive Commenting
Gotcha #2: Magic Numbers
Gotcha #3: Global Variables
Gotcha #4: Failure to Distinguish Overloading from Default Initialization
Gotcha #5: Misunderstanding References
Gotcha #6: Misunderstanding Const
Gotcha #7: Ignorance of Base Language Subtleties
Gotcha #8: Failure to Distinguish Access and Visibility
Gotcha #9: Using Bad Language
Gotcha #10: Ignorance of Idiom
Gotcha #11: Unnecessary Cleverness
Gotcha #12: Adolescent Behavior
Chapter 2. Syntax
Gotcha #13: Array/Initializer Confusion
Gotcha #14: Evaluation Order Indecision
Gotcha #15: Precedence Problems
Gotcha #16: 'for' Statement Debacle
Gotcha #17: Maximal Munch Problems
Gotcha #18: Creative Declaration-Specifier Ordering
Gotcha #19: Function/Object Ambiguity
Gotcha #20: Migrating Type-Qualifiers
Gotcha #21: Self-Initialization
Gotcha #22: Static and Extern Types
Gotcha #23: Operator Function Lookup Anomaly
Gotcha #24: Operator '->' Subtleties
Chapter 3. The Preprocessor
Gotcha #25: '#define' Literals
Gotcha #26: '#define' Pseudofunctions
Gotcha #27: Overuse of '#if'
Gotcha #28: Side Effects in Assertions
Chapter 4. Conversions
Gotcha #29: Converting through 'void *'
Gotcha #30: Slicing
Gotcha #31: Misunderstanding Pointer-to-Const Conversion
Gotcha #32: Misunderstanding Pointer-to-Pointer-to-Const Conversion
Gotcha #33: Misunderstanding Pointer-to-Pointer-to-Base Conversion
Gotcha #34: Pointer-to-Multidimensional-Array Problems
Gotcha #35: Unchecked Downcasting
Gotcha #36: Misusing Conversion Operators
Gotcha #37: Unintended Constructor Conversion
Gotcha #38: Casting under Multiple Inheritance
Gotcha #39: Casting Incomplete Types
Gotcha #40: Old-Style Casts
Gotcha #41: Static Casts
Gotcha #42: Temporary Initialization of Formal Arguments
Gotcha #43: Temporary Lifetime
Gotcha #44: References and Temporaries
Gotcha #45: Ambiguity Failure of 'dynamic_cast'
Gotcha #46: Misunderstanding Contravariance
Chapter 5. Initialization
Gotcha #47: Assignment/Initialization Confusion
Gotcha #48: Improperly Scoped Variables
Gotcha #49: Failure to Appreciate C++'s Fixation on Copy Operations
Gotcha #50: Bitwise Copy of Class Objects
Gotcha #51: Confusing Initialization and Assignment in Constructors
Gotcha #52: Inconsistent Ordering of the Member Initialization List
Gotcha #53: Virtual Base Default Initialization
Gotcha #54: Copy Constructor Base Initialization
Gotcha #55: Runtime Static Initialization Order
Gotcha #56: Direct versus Copy Initialization
Gotcha #57: Direct Argument Initialization
Gotcha #58: Ignorance of the Return Value Optimizations
Gotcha #59: Initializing a Static Member in a Constructor
Chapter 6. Memory and Resource Management
Gotcha #60: Failure to Distinguish Scalar and Array Allocation
Gotcha #61: Checking for Allocation Failure
Gotcha #62: Replacing Global New and Delete
Gotcha #63: Confusing Scope and Activation of Member 'new' and 'delete'
Gotcha #64: Throwing String Literals
Gotcha #65: Improper Exception Mechanics
Gotcha #66: Abusing Local Addresses
Gotcha #67: Failure to Employ Resource Acquisition Is Initialization
Gotcha #68: Improper Use of 'auto_ptr'
Chapter 7. Polymorphism
Gotcha #69: Type Codes
Gotcha #70: Nonvirtual Base Class Destructor
Gotcha #71: Hiding Nonvirtual Functions
Gotcha #72: Making Template Methods Too Flexible
Gotcha #73: Overloading Virtual Functions
Gotcha #74: Virtual Functions with Default Argument Initializers
Gotcha #75: Calling Virtual Functions in Constructors and Destructors
Gotcha #76: Virtual Assignment
Gotcha #77: Failure to Distinguish among Overloading, Overriding, and Hiding
Gotcha #78: Failure to Grok Virtual Functions and Overriding
Gotcha #79: Dominance Issues
Chapter 8. Class Design
Gotcha #80: Get/Set Interfaces
Gotcha #81: Const and Reference Data Members
Gotcha #82: Not Understanding the Meaning of Const Member Functions
Gotcha #83: Failure to Distinguish Aggregation and Acquaintance
Gotcha #84: Improper Operator Overloading
Gotcha #85: Precedence and Overloading
Gotcha #86: Friend versus Member Operators
Gotcha #87: Problems with Increment and Decrement
Gotcha #88: Misunderstanding Templated Copy Operations
Chapter 9. Hierarchy Design
Gotcha #89: Arrays of Class Objects
Gotcha #90: Improper Container Substitutability
Gotcha #91: Failure to Understand Protected Access
Gotcha #92: Public Inheritance for Code Reuse
Gotcha #93: Concrete Public Base Classes
Gotcha #94: Failure to Employ Degenerate Hierarchies
Gotcha #95: Overuse of Inheritance
Gotcha #96: Type-Based Control Structures
Gotcha #97: Cosmic Hierarchies
Gotcha #98: Asking Personal Questions of an Object
Gotcha #99: Capability Queries
Bibliography

Gotcha #54: Copy Constructor Base Initialization

Here are a couple of simple components:

class M { 
 public:
   M();
   M( const M & );
   ~M();
   M &operator =( const M & );
   // . . .
};
class B {
 public:
   virtual ~B();
 protected:
   B();
   B( const B & );
   B &operator =( const B & );
   // . . .
};

Let's leverage these components to produce a new class—and try to get the compiler to do as much work for us as is reasonable:

class D : public B { 
   M m_;
};

While class D doesn't inherit constructors, destructor, or the copy assignment operator from its base class, the compiler will write these operations for us implicitly, leveraging the corresponding implementations of the components. (See Gotcha #49.) For example, the compiler's implementation of D's default constructor will be as a public inline member function. The constructor will first invoke the base class B's default constructor, then the default constructor for the M member. The destructor will, as always, do the inverse: it will first destroy the member, then call the base class destructor.

The copy operations are more interesting. The compiler-generated copy constructor will perform a member-by-member initialization, as if we had written it like this:

D::D( const D &init ) 
   : B( init ), m_( init.m_ )
   {}

The compiler-generated assignment operator performs a member-by-member assignment, as if we had written it like this:

D &D::operator =( const D &that ) { 
   B::operator =( that );
   m_ = that.m_;
   return *this;
}

Suppose we add a data member to our class that doesn't define these operations? For example, we could add a data member that points to a heap-allocated X:

class D : public B { 
 public:
   D();
   ~D();
   D( const D & );
   D &operator =( const D & );
 private:
   M m_;
   X *xp_; // new data member
};

Now we should write all these operations explicitly. The default constructor and the destructor are straightforward, and we can let the compiler do most of the work for us:

D::D() 
   : xp_( new X )
   {}
D::~D()
   { delete xp_; }

The compiler invokes the default constructors and destructors for the base class and member m_ implicitly. It's tempting to think we can get away with the same approach when implementing copy construction and copy assignment, but we can't:

D::D( const D &init ) 
   : xp_( new X(*init.xp_) )
   {}
D &D::operator =( const D &rhs ) {
   delete xp_;
   xp_ = new X(*rhs.xp_);
   return *this;
}

Both these implementations will compile without error and do the wrong thing at runtime. Our copy constructor implementation correctly initializes the member xp_ with a copy of what its initializer's xp_ refers to, but the base class and m_ member are initialized using B's and M's default constructors respectively, rather than their copy constructors. In the case of the assignment, the values of the base class part and m_ are unchanged.

Once you take over the job of writing any of these member functions from the compiler, you're responsible for the entire implementation:

D::D( const D &init ) 
   : B( init ), m_( init.m_ ), xp_( new X(*init.xp_) )
   {}
D &D::operator =( const D &rhs ) {
   if( this != &rhs ) {
       B::operator =( rhs );
       m_ = rhs.m_;
       delete xp_;
       xp_ = new X(*rhs.xp_);
   }
   return *this;
}

This is the case for the default constructor and destructor as well, but the implicit invocation of the default constructors for the base class and m_ member resulted in correct code in that case. I prefer the approach that minimizes typing, but if you prefer, you can be explicit:

D::D() 
   : B(), m_(), xp_( new X )
   {}