Search code examples
c++virtual-functions

Casting pointer to different pointer causes wrong virtual function to be called


#include <iostream>

struct A {
    virtual void a() {
        puts("A");
    }
};

struct B {
    virtual void b() {
        puts("B");
    }
};

struct C {
    virtual void c() {
        puts("C");
    }
};

struct D : public A, public B, public C {
    virtual void c() {
        C::c();
        puts("cd");
    }
};

int main() {
    A* obj = new D;

    obj->a();

    B* b = (B*)obj;
    b->b();
    C* c = (C*)obj;
    c->c();

    return 0;
}

I have this code where I have non virtual multiple inheritance. However, it seems to call the wrong virtual function when I call the functions in the main function. Instead of outputting:

A
B
C
cd

It outputs:

A
A
A

What puzzles me is that when I change the code to doing this:

B* b = (B*)(D*)obj;
b->b();
C* c = (C*)(D*)obj;
c->c();

It outputs what I would expect (see above). Afaik doing a double pointer cast like this wouldn't effect anything and would be optimized out by the compiler. But it seems to be changing what virtual function is being called.

Can someone explain why this would change what virtual function is being called?

Notes:

I printed the pointers at each step, they are the same.

I want to avoid using dynamic_cast (although it does work) as it's too slow for what I need it to do.


Solution

  • Can someone explain why this would change what virtual function is being called?

    Generally, a C-style cast between pointer types won't change the value of the pointer and so will have no effect. There is, however, one exception.

    A cast between a class and a parent or child class can change the value of the pointer. For example:

    class A
    { int a; };
    
    class B
    { int b; };
    
    class C : public A, public B
    ...
    

    Now, a pointer to an instance of class A will probably have the same value as a pointer to its a member and a pointer to an instance of class B will probably have the same value as a pointer to its b member. A pointer to an instance of class C can't have the same value as a pointer to both its A::a and its B::b members since they're distinct objects.

    A function expecting a B* can be passed a C* since a C is a B. Similarly, a function expecting an A* can be passed a C* for the same reason. But at least one of these will require a value change to the pointer.

    So casts between these types will change the values, the others are all no-ops.

    Of course, all of this is UB. You are casting between unrelated types and then dereferencing them.

    I want to avoid using dynamic_cast (although it does work) as it's too slow for what I need it to do.

    That seems very hard to believe.