What will happen if dynamic_cast<void*> casts an object with underlying non-most-derived class?

132 views Asked by At

We know that dynamic_cast<void*> will cast a pointer to the pointer to the most derived object; but what if the underlying object is not the most derived? For example:

class BaseClass { public: virtual void dummy() { std::cout << "Base\n"; } };

class DerivedClass : public BaseClass {
    int a{}; 
public:
    void dummy() { std::cout << "Derived\n"; } 
};
class MostDerivedClass : public DerivedClass {
    int b{}; 
public:
    void dummy() { std::cout << "Most\n"; } 
};

BaseClass* basePtr_d = new DerivedClass, *basePtr_md = new MostDerivedClass;
DerivedClass* derivedPtr = 
    dynamic_cast<DerivedClass*>(basePtr_d); // right
MostDerivedClass* mostDerivedPtr = 
    dynamic_cast<MostDerivedClass*>(basePtr_md); // right
MostDerivedClass* mostDerivedPtr2 = 
    static_cast<MostDerivedClass*>(dynamic_cast<void*>(basePtr_md)); // right
DerivedClass* derivedPtr2 = 
    static_cast<DerivedClass*>(dynamic_cast<void*>(basePtr_d)); // What happens??

What happens for the last case?

3

There are 3 answers

0
YSC On

The rule is:

In dynamic_cast<new-type>(expression), if expression is a pointer to a polymorphic type, and new-type is a pointer to void, the result is a pointer to the most derived object pointed or referenced by expression. See dynamic_cast #4.

It's easy to miss the important distinction: the pointer type and the pointer value, the later being sometimes referred to as the dynamic type of a pointer.

When B derives from A, a pointer of type A* and a pointer of type B* both pointing to the same object of type B might have a different values. The restriction on dynamic_cast<void*> covers this specificity and guarantees a return value for the most derived type, even though the static type of the returned pointer is of course void*. This is still true if there exist a type C deriving from B or A.

0
chrysante On

All the static_cast from void* does is change the type without changing the value of the pointer. It is the same as a reinterpret_cast. In this case everything should be fine, because the MostDerivedClass object has the same address as its DerivedClass subobject.

If this were not the case however, derivedPtr2 would be an invalid pointer. Technically this might still be UB, I'm not sure on that though. Say your MostDerivedClass actually looks like this:

struct X { int i; };

class MostDerivedClass : public X, public DerivedClass {
    int b{}; 
public:
    void dummy() { std::cout << "Most\n"; } 
};

Now doing a static_cast from MostDerivedClass* to DerivedClass* changes the value of the pointer, possibly by adding an offset of four bytes. Now this line

DerivedClass* derivedPtr2 = 
    static_cast<DerivedClass*>(dynamic_cast<void*>(basePtr_d)); // What happens??

statically casts a void pointer (which points to the MostDerivedClass object or also the X subobject) to DerivedClass*, which is essentially a reinterpret_cast. So you end up with a pointer of type DerivedClass which actually points to the X-subobject, not to the DerivedClass subobject. So the derivedPtr2 pointer you end up with is invalid.

0
Caleth On

The last case is the same as the previous case. You have a BaseClass *, you do a pointer conversion which gives you the value of a pointer to the most derived object, and then you statically cast that value to the appropriate type. At no point does the type MostDerivedClass get involved in these conversions.

"most derived class" is not a global property of the types in your program. It refers to the particular object you have a pointer or reference to. For objects that you get from new, it is the type that was used in the new expression.

basePtr_d points to the BaseClass subobject of a DerivedClass instance, and basePtr_md points to the BaseClass subobject of a MostDerivedClass instance.