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 #45: Ambiguity Failure of dynamic_cast

Sure, you feel guilty about it. You probably won't discuss it with your colleagues. It may even start to affect your personal relationships. But when your back is to the wall, you're dealing with a poorly designed module and impossible demands from your management, and you need to be finished yesterday, it may be time to employ a dynamic_cast.

Let's say the problem has to do with the need to determine whether a particular screen object is an entry screen rather than some other type of screen. The problem is that you're in the middle of some otherwise generic code that should apply to screens in general. Your first impulse might be to augment the interface of all screen types to provide the required information:

class Screen { 
 public:
   //...
   virtual bool isEntryScreen() const
       { return false; }
};
class EntryScreen : public Screen {
 public:
   bool isEntryScreen() const
       { return true; }
};
//  . . .
Screen *getCurrent();
//  . . .
if( getCurrent()->isEntryScreen() )
   //  . . .

The problem with this approach is that it legitimizes asking a prying question of a Screen object. The base Screen class is explicitly inviting maintainers to ask the personal question "Are you an EntryScreen?" With that, the floodgates are open, and future maintainers will add more prying questions (see Gotcha #98):

class Screen { 
 public:
   //...
   virtual bool isEntryScreen() const
       { return false; }
   virtual bool isPricingScreen() const
       { return false; }
   virtual bool isSwapScreen() const
       { return false; }
   // ad infinitum . . .
};

Of course, the presence of such an interface pretty much guarantees it will be used:

// . . . 
if( getCurrent()->isEntryScreen() )
   // . . .
else if( getCurrent()->isPricingScreen() )
   // . . .
else if( getCurrent()->isSwapScreen() )
   // . . .

It's kind of like a switch, except slower and less maintainable. A lesser evil is just to bite the bullet and perform a single dynamic_cast. The use of the cast will, one hopes, be sufficiently hidden not to inspire imitation and will be removed at some future date when the code is refactored:

if( EntryScreen *es = dynamic_cast<EntryScreen *>(sp) ) { 
   // do stuff with the entry screen...
}

If the cast succeeds, es will refer to an EntryScreen, which may be the actual type of the screen object or simply an EntryScreen subobject of a more specialized screen object. But what does failure mean?

A dynamic_cast can produce a null result for any of four reasons. First, the cast can be incorrect. If sp doesn't refer to an EntryScreen or something derived from an EntryScreen, the cast will fail. Second, if sp is null, the result of the cast will also be null. Third, the cast will fail if we attempt to cast to or from an inaccessible base class. Finally, the cast can fail due to an ambiguity.

Type conversion ambiguities are uncommon in well-designed hierarchies, but it's possible to get into trouble with hierarchies that are poorly constructed or improperly accessed.

Figure 4-4 shows a simple multiple-inheritance hierarchy. We'll assume A is polymorphic (it has a virtual function) and that only public inheritance is used. In this case, a D object has two A subobjects; that is, at least one A is a nonvirtual base class:

D *dp = new D; 
A *ap = dp; // error! ambiguous
ap = dynamic_cast<A *>(dp); // error! ambiguous
Figure 4-4. A multiple-inheritance hierarchy without virtual base classes. A D complete object contains two A subobjects.

graphics/04fig04.gif

The initialization of ap is ambiguous, because it can refer to two reasonable A addresses. However, once we have the address of one of the two A subobjects, reference to any of the other subtypes in the hierarchy is unambiguous:

B *bp = dynamic_cast<B *>(ap); // works 
C *cp = dynamic_cast<C *>(ap); // works

No matter which A subobject ap refers to, converting it to refer to the B or C subobjects or to the D complete object is unambiguous, because the complete object contains a single instance of each of those subobjects.

It's interesting to note that if both As were virtual base classes, there would be no ambiguity, since a D object would then contain a single A subobject:

D *dp = new D; 
A *ap = dp; // OK, not ambiguous
ap = dynamic_cast<A *>(dp); // OK, not ambiguous

We can reintroduce ambiguity by making the hierarchy a little more complex, as in Figure 4-5. For this modified hierarchy, the earlier ambiguity is not present, because a D object still contains a single A subobject:

A *ap = new D; // no ambiguity 
Figure 4-5. A multiple-inheritance hierarchy with virtual and nonvirtual inheritance of multiple subobjects of the same type. A D complete object contains a single A subobject but two E subobjects.

graphics/04fig05.gif

However, we now have an ambiguity going the other way:

E *ep = dynamic_cast<E *>(ap); // fails! 

The pointer ap could be converted to either of two E subobjects. We can circumvent this ambiguity by being more specific:

E *ep = dynamic_cast<B *>(ap); // works 

A D contains a single B subobject, so converting an A * into a B * is unambiguous, and the subsequent conversion from B * to its public base doesn't require a cast. However, note that this solution embeds detailed knowledge of the structure of the hierarchy below classes A and E into the code. It's better to simplify the structure of the hierarchy to avoid the possibility of dynamic ambiguity.

Since we're on the subject of dynamic_cast, we should point out a couple of subtleties of its semantics. First, a dynamic_cast is not necessarily dynamic, in that it may not perform a runtime check. When performing a dynamic_cast from a derived class pointer (or reference) to one of its public base classes, no runtime check is needed, because the compiler can determine statically that the cast will succeed. Of course, no cast of any kind is needed in this case, since conversion from a derived class to its public base classes is predefined. (While language rules of this type may initially seem extraneous, they often facilitate template programming, where the types to be manipulated are generally not known in advance.)

It's also legal to cast a pointer or reference to a polymorphic type to void *. In this case, the result will refer to the start of the "most derived," or complete, object to which the pointer refers. Of course, we still won't know what we're pointing to, but at least we'll know where it is …