tly
tly

Reputation: 1262

Multiple inheritance cast not working as expected

I recently had a problem with casts and multiple inheritance: I needed to cast a Base* to Unrelated*, because a specific Derived class derives the Unrelated class.

This is a short example:

#include <iostream>

struct Base{
    virtual ~Base() = default; 
};

struct Unrelated{
    float test = 111;    
};

struct Derived : Base,Unrelated{};

int main(){
    Base* b = new Derived;
    Unrelated* u1 = (Unrelated*)b;
    std::cout << u1->test << std::endl; //outputs garbage
    Unrelated* y = dynamic_cast<Unrelated*>(b);
    std::cout << y->test << std::endl; //outputs 111
}

The first cast clearly doesnt work, but the second one did work. My question is: Why did the second cast work? Shouldnt dynamic_cast only work for cast to a related class type? I thought there wasnt any information about Unrelated at runtime because it is not polymorphic.

Edit: I used colirus gcc for the example.

Upvotes: 2

Views: 1267

Answers (3)

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145204

The first cast (Unrelated*)b doesn't work because you're treating the Base class sub-object, containing probably just a vtable pointer, as an Unrelated, containing a float.

Instead you can cast down and up, static_cast<Unrelated*>( static_cast<Derived*>( b ) ).

And this is what dynamic_cast does for you, since Base is a polymorphic type (at least one virtual method) which allows dynamic_cast to inspect the type of the most derived object.

In passing, dynamic_cast<void*>( b ) would give you a pointer to the most derived object.

However, since you know the types there's no need to invoke the slight overhead of a dynamic_cast: just do the down- and up-casts.


Instead of the C style cast (Unrelated*)b you should use the corresponding C++ named cast or casts, because C style casts of pointers can do things you'd not expect, and because the effect can change completely when types are changed during maintenance.

The C style cast will maximum do 2 C++ named casts. In this case the C style cast corresponds to a reinterpret_cast. The compiler will not allow any other named cast here.

Which is a warning sign. ;-)

In contrast, the down- and up-casts are static_casts, which usually are benign casts.


All that said, the best is to almost completely avoid casts by using the top secret technique:

Don't throw away type information in the first place.

I.e., in the example code, just use Derived* as the type of the pointer.

Upvotes: 5

Mark Ransom
Mark Ransom

Reputation: 308101

With multiple inheritance, the Derived object consists of two sub-objects, one Base and one Unrelated. The compiler knows how to access whichever part of the object it needs, typically by adding an offset to the pointer (but that's an implementation detail). It can only do that when it knows the actual type of a pointer. By using a C-style cast, you've told the compiler to ignore the actual type and treat that pointer value as a pointer to something else. It no longer has the information necessary to properly access the sub-object you desire, and it fails.

dynamic_cast allows the compiler to use run-time information about the object to locate the proper sub-object contained within it. If you were to output or examine the pointer values themselves, you'd see that they are different.

Upvotes: 2

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136208

dynamic_cast works because the dynamic type of object pointed to by a pointer to its base class is related to Unrelated.

Keep in mind that dynamic_cast requires a virtual table to inspect the inheritance tree of the object at run-time.

The C-style cast (Unrelated*)b does not work because the C-style cast does const_cast, static_cast, reinterpret_cast and more, but it does not do dynamic_cast.

I would suggest avoiding C-style casts in C++ code because they do so many things as opposed to precise C++ casts. My colleagues who insist on using C-style cast still occasionally get them wrong.

Upvotes: 8

Related Questions