[ Team LiB ] Previous Section Next Section

Gotcha #38: Casting under Multiple Inheritance

Under multiple inheritance, an object may have many valid addresses. Each base class subobject of a complete object may have a unique address, and each of these addresses is a valid address for the complete object. (In a poorly designed single-inheritance hierarchy, an object might have two valid addresses. See Gotcha #70.)



class A { /* . . .*/ }; 


class B { /* . . .*/ };


class C : public A, public B { /* . . .*/ };


// . . .


C *cp = new C;


A *ap = cp; // OK


B *bp = cp; // OK


In the example above, the B subobject of the C complete object is likely to be allocated at a fixed offset, or "delta," from the start of the C object. Conversion of the derived class pointer cp to B * will therefore result in adjustment of cp by the delta to produce a valid B address. The conversion is type safe, efficient, and automatically implemented by the compiler.

Similarly, the existence of multiple valid addresses for an object forces C++ to be precise about the meaning of pointer comparison:



if( bp == cp ) // . . . 


The question we are asking is not "Do these two pointers contain the same bit pattern?" but rather "Do these two pointers refer to the same object?" The implementation of the condition may be more complex, but it's still efficient, safe, and automatic. The compiler probably implements the pointer comparison in a way similar to the following:



if( bp ? (char *)bp-delta==(char *)cp : cp==0 ) 


Both old- and new-style casts may be used to perform conversions that respect delta arithmetic on class object addresses. However, unlike the conversions above, there is no guarantee that the result of the cast will be a valid address. (A dynamic_cast will give such a guarantee but may introduce other problems. See Gotchas #97, #98, and #99.)



B *gimmeAB(); 


bp = gimmeAB();


cp = static_cast<C *>(bp); cp = (C *) bp;


typedef C *CP;


cp = CP( bp );


All three casts will perform delta arithmetic on bp, but the result will be valid only if the B object to which bp refers is part of a containing C object. If this assumption is incorrect, the result will be a bad address, equivalent to some creative C-style code:



cp = (C *)((char *)bp–delta) 


A reinterpret_cast will do just what it says. It will simply reinterpret the bit pattern of its argument to mean something else, probably without modifying the bits. Effectively, it "turns off" the delta arithmetic. (To be perfectly precise, the standard says the behavior of this cast is implementation-defined, but it's universally understood to "turn off hierarchy traversal." However, this behavior is not guaranteed by the standard, and reinterpret_cast may actually change the bit representation of the pointer.)



cp = reinterpret_cast<C *>(bp);  // yes, I do want to dump core . . . 


All these uses of casts are asking the object referred to by the B pointer to take on more responsibility than its interface can guarantee. We have a bad design, because we know too little about an object's capabilities and are using a static cast to force the object into a role it may not be able to fulfill. It's best to avoid static casts on class objects. Later, I'll argue that it's best to avoid dynamic casts as well. You get the picture.

    [ Team LiB ] Previous Section Next Section