Click here to Skip to main content
15,868,016 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have encountered some strange behavior with the dynamic_cast that I don't understand.

Here is the code reduced to the problem:

#include <iostream>

class A { virtual void f () {}; };
class B { virtual void g () {}; };
class C { virtual void h () {}; };

class X : public A, public B, public C
{
public:

        virtual void i () {};
};


int main()
{
        X*      pX = new X ();

        std::cout << "original X ptr: " << std::hex << pX << std::endl;

        void* pVoid = pX;

        A*      pA = reinterpret_cast<A*> ( pVoid );
        B*      pB = reinterpret_cast<B*> ( pVoid );
        C*      pC = reinterpret_cast<C*> ( pVoid );

        std::cout << "A ptr: " << std::hex << pA << std::endl;
        std::cout << "B ptr: " << std::hex << pB << std::endl;
        std::cout << "C ptr: " << std::hex << pC << std::endl;

        X*      pXa = dynamic_cast<X*> ( pA );
        X*      pXb = dynamic_cast<X*> ( pB );
        X*      pXc = dynamic_cast<X*> ( pC );

        std::cout << "X (dynamic casted from A) ptr: " << std::hex << pXa << std::endl;
        std::cout << "X (dynamic casted from B) ptr: " << std::hex << pXb << std::endl;
        std::cout << "X (dynamic casted from C) ptr: " << std::hex << pXc << std::endl;
}


and here the result:

original X ptr: 0x1148010
A ptr: 0x1148010
B ptr: 0x1148010
C ptr: 0x1148010
X (dynamic casted from A) ptr: 0x1148010
X (dynamic casted from B) ptr: 0
X (dynamic casted from C) ptr: 0


Why do the last two dynamic_cast operations return NULL and not the pointer to the original object?

I used g++ 4.8.5 compiler, but also tried other compilers.

What I have tried:

I played around with the inheritance order of class X and found that the dynamic_cast is only working for the first parent class.
Posted
Updated 29-Jan-22 2:12am

1 solution

You can look up what the standard has to say about dynamic_cast when multiple inheritance is involved, but I'm going to live dangerously and not bother.

The first (hidden) data member of every object that has a virtual function is a vptr. It points to a table of function pointers, one for each virtual function in that class. That table is shared by all objects in the class, with each vptr pointing to it. The compiler assigns each virtual function an index into that table so that a virtual function can then invoked by
C++
vptr->table[index](args);
If an object belongs to a class that uses multiple inheritance, it will have more than one vptr, and they will appear consecutively as the first members in the object.

When you dynamic_cast X* to an A*, it isn't a problem because A's vptr is the first one in an X. However, when you try to dynamic_cast X* to a B* or C*, the resulting pointer would have to reference a vptr that is not at the top of the object. So if you later did a delete on that pointer, it would think the object started at the location of that vptr when it actually didn't. Returning the object's memory to the heap would cause an exception or heap corruption, because it doesn't start at the same address as the block that was originally allocated. So the standard probably says that, in the case of multiple inheritance, an object of that class can only be dynamically cast to the first of its multiple base classes.

EDIT: If the target class (B or C) didn't have any virtual functions, the dynamic_cast would probably work: that class doesn't have a vptr, so the pointer could still reference the top of the original object (A's vptr).

However, if any of A, B, or C had a data member, my guess is that the dynamic_cast would always fail. Why? Because to access a data member, the compiler takes a pointer to the object and adds an offset. But if there are extra vptrs in there because the underlying class uses multiple inheritance, that offset would be wrong.
 
Share this answer
 
v6
Comments
_Asif_ 29-Jan-22 9:12am    
5+
Greg Utas 29-Jan-22 9:31am    
Thanks!
Joerg Michels 30-Jan-22 8:57am    
Hi Greg,

thanks for your advice. I think I need to look deeper into the standard as you suggested.

As far as I understand it, the problem is related to the cast via the void pointer. A direct upcast from type X* to B* and then a downcast back to type X* works without problems.

I'll have to take a closer look at that...
Greg Utas 30-Jan-22 9:05am    
You can cast to whatever you want, but you risk blowing up if you
(a) use delete on a pointer that isn't the first vptr in the original object, or
(b) access a data member when there is another vptr in between.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900