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 #5: Misunderstanding References

There are two common problems with references. First, they're often confused with pointers. Second, they're underused. Many current uses of pointers in C++ are really C holdovers that should be ceded to references.

A reference is not a pointer. A reference is an alias for its initializer. Essentially, the only thing one can do with a reference is initialize it. After that, it's simply another way of referring to its initializer. (But see Gotcha #44.) A reference doesn't have an address, and it's even possible that it might not occupy any storage:

int a = 12; 
int &ra = a;
int *ip = &ra; // ip refers to a
a = 42; // ra == 42

For this reason, it's illegal to attempt to declare a reference to a reference, a pointer to a reference, or an array of references. (Though the C++ standards committee has discussed allowing references to references in the future, at least in some contexts.)

int &&rri = ra; // error! 
int &*pri; // error!
int &ar[3]; // error!

References can't be const or volatile, because aliases can't be const or volatile, though a reference can refer to an entity that is const or volatile. An attempt to declare a reference const or volatile directly is an error:

int &const cri = a; // should be an error . . . 
const int &rci = a; // OK

Strangely, it's not illegal to apply a const or volatile qualifier to a type name that is of reference type. Rather than cause an error, in this case the qualifier is ignored:

typedef int *PI; 
typedef int &RI;
const PI p = 0; // const pointer
const RI r = a; // just a reference!

There are no null references, and there are no references to void:

C *p = 0; // a null pointer 
C &rC = *p; // undefined behavior
extern void &rv; // error!

A reference is an alias, and an alias has to refer to something.

Note, however, that a reference does not have to refer to a simple variable name. It's sometimes convenient to bind a reference to an lvalue (see Gotcha #6) resulting from a more complex expression:

int &el = array[n-6][m-2]; 
el = el*n-3;
string &name = p->info[n].name;
if( name == "Joe" )
   process( name );

A reference return from a function allows assignment to the result of a call. The canonical example of this is an index function for an abstract array:

gotcha05/array.h

template <typename T, int n> 
class Array {
 public:
   T &operator [](int i)
       { return a_[i]; }
   const T &operator [](int i) const
       { return a_[i]; }
   // . . .
 private:
   T a_[n];
};

The reference return permits a natural syntax for assignment to an array element:

Array<int,12> ia; 
ia[3] = ia[0];

References may also be used to provide additional return values for functions:

Name *lookup( const string &id, Failure &reason ); 
// . . .
string ident;
// . . .
Failure reasonForFailure;
if( Name *n = lookup( ident, reasonForFailure ) ) {
   // lookup succeeded . . .
}
else {
   // lookup failed. check reason . . .
}

Casting an object to a reference type has a very different effect from the same cast to the nonreference version of the type:

char *cp = reinterpret_cast<char *>(a); 
reinterpret_cast<char *&>(a) = cp;

In the first case, we're converting an integer into a pointer. (We're using reinterpret_cast in preference to an old-style cast, like (char *)a. See Gotcha #40.) The result is a copy of the integer's value, interpreted as a pointer.

The second cast is very different. The result of the cast to reference type is a reinterpretation of the integer object itself as a pointer. It's an lvalue, and we can assign to it. (Whether we will then dump core is another story. Use of reinterpret_cast generally implies "not portable.") An analogous attempt with a cast to nonreference will fail, because the result of the cast is an rvalue, not an lvalue:

reinterpret_cast<char *>(a) = 0; // error! 

A reference to an array preserves the array bound. A pointer to an array does not:

int ary[12]; 
int *pary = ary; // point to first element
int (&rary)[12] = ary; // refer to whole array
int ary2[3][4];
int (*pary2)[4] = ary2; // point to first element
int (&rary2)[3][4] = ary2; // refer to whole array

This property can be of occasional use when passing arrays to functions. (See Gotcha #34.)

It's also possible to bind a reference to a function:

int f( double ); 
int (* const pf)(double) = f; // const pointer to function
int (&rf)(double) = f; // reference to function

There's not much practical difference between a constant pointer to function and a reference to function, except that the pointer can be explicitly dereferenced. As an alias, the reference cannot, although it can be converted implicitly into a pointer to function and then dereferenced:

a = pf( 12.3 ); // use pointer 
a = (*pf)(12.3); // use pointer
a = rf( 12.3 ); // use reference
a = f( 12.3 ); // use function
a = (*rf)(12.3); // convert ref to pointer and deref
a = (*f)(12.3); // convert func to pointer and deref

Distinguish references and pointers.