Optimus Prime
Optimus Prime

Reputation: 429

Casting between the two parents of a multi inherited class with virtual functions leads to weird behaviors

Below is the code. I don't understand why it behaves like this:

#include <iostream>
using namespace std;

class FooInterface {
public:
    virtual ~FooInterface() = default;
    virtual void Foo() = 0;
};

class BarInterface {
public:
    virtual ~BarInterface() = default;

    virtual void Bar() = 0;
};

class Concrete : public FooInterface, public BarInterface {
public:
    void Foo() override { cout << "Foo()" << endl; }
    void Bar() override { cout << "Bar()" << endl; }
};

int main() {
    Concrete c;
    c.Foo();
    c.Bar();

    FooInterface* foo = &c;
    foo->Foo();

    BarInterface* bar = (BarInterface*)(foo);
    bar->Bar(); // Prints "Foo()" - WTF?
}

The last statement bar->Bar() prints "Foo()" which confused me. This comes from the following blog: https://shaharmike.com/cpp/vtable-part4/. Basically it relates to structure of the vtable of the class and how compiler handles the cast between two parent classes of a multi inherited class with virtual functions. Can anybody help me understand this?

Upvotes: 0

Views: 128

Answers (1)

1201ProgramAlarm
1201ProgramAlarm

Reputation: 32732

When you write (BarInterface*)(foo);, you're lying to the compiler. You're telling it that foo is really a pointer to BarInterface, so the compiler will believe you. Since it isn't, you get Undefined Behavior when you attempt to dereference the pointer. Discussing how a compiled program behaves in the presence of Undefined Behavior is often pointless.

In this case, the way your compiler has populated the vtables, the entry for FooInterface::Foo appears to be in the same place as the entry for BarInterface::Bar. As a result, when you call bar->Bar(), the compiler looks into the FooInterface vtable, finds the entry for FooInterface::Foo, and calls that. If the class layouts would be different, or the function signatures different, far more serious consequences would ensue.

The solution, of course, is to use a dynamic_cast which can perform the necessary sideways cast.

Upvotes: 2

Related Questions