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 #47: Assignment/Initialization Confusion

Technically, assignment has little to do with initialization. They're separate operations, used in different circumstances. Initialization is the process of turning raw storage into an object. For a class object, this could entail setting up internal mechanisms for virtual functions and virtual base classes, runtime type information, and other type-dependent information (see Gotchas #53 and #78). Assignment is the process of replacing the existing state of a well-defined object with a new state. Assignment doesn't affect internal mechanisms that implement type-dependent behavior of an object. Assignment is never performed on raw storage.

Idiomatically, however, if copy construction semantics are important in one set of circumstances, chances are that copy assignment semantics are important in the others, and vice versa. Forgetting to consider both assignment and initialization will result in bugs:

class SloppyCopy { 
 public:
   SloppyCopy &operator =( const SloppyCopy & );
   // Note: compiler default SloppyCopy(const SloppyCopy &) . . .
 private:
   T *ptr;
};

void f( SloppyCopy ); // pass by value

SloppyCopy sc;
f( sc );       // alias what ptr points to, probable error

Argument passing is accomplished with initialization, not assignment; the formal argument to f is initialized by the sc actual argument. The initialization will be accomplished with SloppyCopy's copy constructor. In the absence of an explicitly declared copy constructor, the compiler will write one. In this case, the compiler's version will be incorrect. (See Gotchas #49 and #53.)

The idiomatic assumption is that, even though copy construction and copy assignment are different operations, they should have similar, or conformant, meaning:

extern T a, b; 
b = a;
T c( a );

In the code above, users of the type T would expect the values of b and c to be conformant. In other words, it should be immaterial to subsequent execution whether an object of type T received its current value as the result of an assignment or an initialization. This assumption of conformance is so ingrained in the C++ programming community that the standard library depends on it:

gotcha47/rawstorage.h

template <class Out, class T> 
class raw_storage_iterator
   : public iterator<output_iterator_tag,void,void,void,void> {
 public:
   raw_storage_iterator& operator =( const T& element );
   // . . .
 protected:
   Out cur_;
};
template <class Out, class T>
raw_storage_iterator<Out, T> &
raw_storage_iterator<Out,T>::operator =( const T &val ) {
   T *elem = &*cur_; // get a ptr to element
   new ( elem ) T(val); // placement and copy constructor
   return *this;
}

A raw_storage_iterator is used to assign to uninitialized storage. Ordinarily, an assignment operator requires that both its arguments be properly initialized objects; otherwise, a problem is likely when the assignment attempts to "clean up" the left argument before setting its new value. For example, if the objects being assigned contain a pointer to a heap-allocated buffer, the assignment will typically delete the buffer before setting the new value of the object. If the object is uninitialized, the deletion of the uninitialized pointer member will result in undefined behavior:

gotcha47/rawstorage.cpp

struct X { 
   T *t_;
   X &operator =( const X &rhs ) {
       if( this != &rhs )
           { delete t_; t_ = new T(*rhs.t_); }
       return *this;
   }
   // . . .
};
// . . .
X x;
X *buf = (X *)malloc( sizeof(X) ); // raw storage . . .
X &rx = *buf; // foul trickery . . .
rx = x; // probable error!

The copy algorithm from the standard library copies an input sequence to an output sequence, using assignment to perform the copy of each element:

template <class In, class Out> 
Out std::copy( In b, In e, Out r ) {
   while( b != e )
       *r++ = *b++; // assign src element to dest element
   return r;
}

Use of copy on an uninitialized array of X will most probably fail:

gotcha47/rawstorage.cpp

X a[N]; 
X *ary = (X *)malloc( N*sizeof(X) );
copy( a, a+N, ary ); // assign to raw storage!

Assignment is a bit like (but not exactly like!) a destruction followed by a copy construction. The raw_storage_iterator allows assignment to uninitialized storage by reinterpreting the assignment as a copy construction, skipping the problematic "destruction" step. This will work only under the assumption that copy assignment and copy construction produce acceptably similar results.

gotcha47/rawstorage.cpp

raw_storage_iterator<X *, X> ri( ary ); 
copy( a, a+N, ri ); // works!

This is not to imply that the designer of class X must be intimately aware of all the (admittedly difficult and obscure) details of the standard library to produce a correct implementation. However, the designer does have to be aware of the general, idiomatic assumption that copy initialization and copy assignment are conformant. An abstract data type that doesn't support this conformance can't be leveraged effectively with the standard library and will be less useful than a type that does conform.

Another common misapprehension is that assignment is somehow involved in the following initialization:

T d = a; // not an assignment 

That = symbol is not an assignment operator, and d is initialized by a. This is fortunate, since otherwise we'd have an assignment to uninitialized storage. (But see Gotcha #56.)