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 #29: Converting through void *

Even C programmers know that a void * is second cousin to a cast and should be avoided to the extent possible. As with a cast, converting a typed pointer to void * removes all useful type information. Typically, the original type of the pointer must be "remembered" and restored when the void * is used. If the type is resupplied correctly, everything will work fine (except, of course, that having to remember types for later casting implies that the design needs work).

void *vp = new int(12); 
// . . .
int *ip = static_cast<int *>(vp); // will work

Unfortunately, even this simple use of void * can open the door to portability problems. Remember that static_cast is the cast operator we use (when we must cast) for relatively safe and portable conversions. For example, one might use a static_cast to cast from a base class pointer to a publicly derived class pointer. For unsafe, platform-dependent conversions, we're forced to use reinterpret_cast. For example, one might use a reinterpret_cast to cast from an integer to a pointer or between pointers to unrelated types:

char *cp = static_cast<char *>(ip); // error! 
char *cp = reinterpret_cast<char *>(ip); // works.

The use of reinterpret_cast is a clear indication to you and to the readers and maintainers of your code that you're not only casting but that you're casting in a potentially nonportable way. Use of a void * intermediary allows that important warning to be circumvented:

char *cp = static_cast<char *>(vp); // put int addr into a char *! 

It gets worse. Consider a user interface that allows the address of a "Widget" to be stored and later retrieved:

typedef void *Widget; 
void setWidget( Widget );
Widget getWidget();

Users of this interface recognize that they have to remember the type of Widget they set, so they can restore its type information when it's retrieved:

// In some header file . . . 
class Button {
   // . . .
};
class MyButton : public Button {
   // . . .
};
// elsewhere . . .
MyButton *mb = new MyButton;
setWidget( mb );

// somewhere else entirely . . .
Button *b = static_cast<Button *>(getWidget()); // might work!

This code will usually work, even though we lose some type information when we extract the Widget. The stored Widget refers to a MyButton but is extracted as a Button. The reason this code will often work has to do with the likely way that the storage for a class object is laid out in memory.

Typically, a derived class object contains the storage for its base class subobject starting at offset 0, as if its base class part were the first data member of the derived class, and simply appends any additional derived class data below that, as in Figure 4-1. Therefore, the address of a derived class object is generally the same as that of its base class. (Note, however, that the standard guarantees correct results only if the address in the void * is converted to exactly the same type used to set the void *. See Gotcha #70 for one way this code could fail even under single inheritance.)

Figure 4-1. Likely layout of a derived class under single inheritance

graphics/04fig01.gif

However, this code is fragile, in that a remote change during maintenance may introduce a bug. In particular, a straightforward and proper use of multiple inheritance may break the code:

// in some header file . . . 
class Subject {
   // . . .
};
class ObservedButton : public Subject, public Button {
   // . . .
};
// elsewhere . . .
ObservedButton *ob = new ObservedButton;
setWidget( ob );
// . . .
Button *badButton = static_cast<Button *>(getWidget()); //
disaster!

The problem is with the layout of the derived class object under multiple inheritance. An ObservedButton has two base class parts, and only one of them can have the same address as the complete object. Typically, storage for the first base class (in this case, Subject) is placed at offset 0 in the derived class, followed by the storage for subsequent base classes (in this case, Button), followed by any additional derived class data members, as in Figure 4-2. Under multiple inheritance, a single object commonly has multiple valid addresses.

Figure 4-2. Likely layout of an object under multiple inheritance. An ObservedButton object contains subobjects for both its Subject and Button base classes. Loss of type information caused badButton to refer to a non-Button address.

graphics/04fig02.gif

Ordinarily this is not a problem, since the compiler is aware of the various offsets and can perform the correct adjustments at compile time:

Button *bp = new ObservedButton; 
ObservedButton *obp = static_cast<ObservedButton *>(bp);

In the code above, bp correctly points to the Button part of the ObservedButton object, not to the start of the object. When we cast from a Button pointer to an ObservedButton pointer, the compiler is able to adjust the address so that it points to the start of the ObservedButton object. It's not hard, since the compiler knows the offset of each base class part within a derived class, as long as it knows the type of the base and derived classes.

And that's our problem. When we use setWidget, we throw away all useful type information. When we cast the result of getWidget to Button, the compiler can't perform the adjustment to the address. As a result, the Button pointer is actually referring to a Subject!

Void pointers do have their uses, as do casts, but they should be used sparingly. It's never a good idea to use a void * as part of an interface that requires one use of the interface to resupply type information lost through another use.