简体   繁体   中英

Does multiple inheritance order matters?

I have come up with some confusing situations. Below are they.

#include <iostream>

using namespace std;

class Base {
public:
    Base(int num) : data(num) {
        cout << "BASE : " << num << endl;
    }
    virtual ~Base() = default;

    virtual void func1() {
        cout << "Base func1 called : " << data << endl;
    }

    virtual void func3() {
        cout << "Base func3 called : " << data << endl;
    }

private:
    int data;
};

class Interface {
public:
    Interface() {
        cout << "INTERFACE : " << endl;
    }
    virtual ~Interface() = default;

    virtual void func2() {
        cout << "Interface func2 called" << endl;
    }
};

class Derived : public Interface, public Base {
public:
    Derived() : Base(0) {
        cout << "DERIVED : hh" << endl;
    }
    virtual ~Derived() = default;

    virtual void func1() override {
        cout << "Derived fuc1 called" << endl;
    }

    virtual void func3() override {
        cout << "Derived fuc3 called" << endl;
    }

    virtual void func2() override {
        cout << "Derived fuc2 called" << endl;
    }


};

int main() {
    //Interface* a = new Derived(); // derived func2 called
    //Base* a = new Derived();    // derived func1 called
    //Derived* a = new Derived(); // derived func2 called
    void* a = new Derived();      // derived func1 called
    auto b = (Interface*)a;
    b->func2();
    
    ...
}

When executing b->func2() , the result is different by the explicit type of variable a.

The results are in the comments.

Why are they different when executing b->func2() ?

void* a = new Derived();      // derived func1 called
auto b = (Interface*)a;

This is undefined behavior. The usual semantics of converting a pointer to a derived class to a pointer to its base class apply only, well, when you convert a pointer to a derived class into a pointer to its base class. But that's not what happens here.

An intermediate conversion into some other pointer, like a void * voids all warranty (pun not intended).

void* a = new Derived();
auto b = (Interface*)a;

This is Undefined Behavior . You are not casting the void* back to the same type that was stored in it ( Derived* ).

If you want to cast the void* to Interface* then you need to store an Interface* in the void* to begin with, eg:

void* a = static_cast<Interface*>(new Derived());
auto b = static_cast<Interface*>(a);

When a virtual method is called via an object pointer, the compiler dereferences the object pointer to access a hidden pointer to a vtable belonging to the type that the object pointer is pointing at, and then it indexes into that vtable to know which class method to call.

The Derived object looks something like this in memory (details may vary by compiler, but this is the gist of it 1 ):

                       +-------+
                   +-> | ~Base |--> &Derived::~Derived()
                   |   | func1 |--> &Derived::func1()
+---------------+  |   | func3 |--> &Derived::func3()
|   Base vmt    |--+   +-------+
|---------------|        +------------+
| Interface vmt |------> | ~Interface |--> &Derived::~Derived()
|---------------|        | func2      |--> &Derived::func2()
|  Derived vmt  |--+     +------------+
+---------------+  |   +------------+
                   +-> | ~Base      |--> &Derived::~Derived()
                       | func1      |--> &Derived::func1()
                       | func3      |--> &Derived::func3()
                       | ~Interface |--> &Derived::~Derived()
                       | func2      |--> &Derived::func2()
                       | ~Derived   |--> &Derived::~Derived()
                       +------------+

A Derived object consists of everything in Base , Interface and Derived cobbled together. Each class still has its own pointer to a virtual method table belonging to that class. But since Derived implements all of the virtual methods, a Derived object has multiple vtable pointers, and they all refer to Derived 's methods.

1: for efficiency, it is likely that Derived only has 1 vtable and all 3 of its vmt pointers are pointing to different areas of that single table. But that is an implementation detail, not important for purposes of this answer.

When a is declared as Interface* , it points at the Interface portion of the Derived object:

     +---------------+
     |   Base vmt    |
     |---------------|
a -> | Interface vmt |
     |---------------|
     |  Derived vmt  |
     +---------------+

Type-casting a to Interface* is effectively a no-op, resulting in b being an Interface* pointer to the Interface portion of the Derived object:

     +---------------+
     |   Base vmt    |
     |---------------|
b -> | Interface vmt |
     |---------------|
     |  Derived vmt  |
     +---------------+

So calling b->func2() uses Interface' s vtable, jumping to 2nd entry which is Derived::func2() , as expected.

When a is declared as Base* , it points at the Base portion of the Derived object:

     +---------------+
a -> |   Base vmt    |
     |---------------|
     | Interface vmt |
     |---------------|
     |  Derived vmt  |
     +---------------+

But type-casting a to Interface* is Undefined Behavior , because Base and Interface are not related to each other, resulting in b being an Interface* pointer to the Base portion of the Derived object:

     +---------------+
b -> |   Base vmt    |
     |---------------|
     | Interface vmt |
     |---------------|
     |  Derived vmt  |
     +---------------+

So calling b->func2() mistakenly uses Base 's vtable instead of Interface 's vtable, jumping to the 2nd entry which is Derived::func1() , unexpectedly.

Had you used static_cast instead of a C-style cast here, the compile would have simply failed instead (this is why you should avoid using C-style casts in C++.).

When a is declared as Derived* , it points at the Derived portion of the Derived object:

     +---------------+
     |   Base vmt    |
     |---------------|
     | Interface vmt |
     |---------------|
a -> |  Derived vmt  |
     +---------------+

Type-casting a to Interface* is well-defined since Interface is a base of Derived , resulting in b being an Interface* pointer to the Interface portion of the Derived object:

     +---------------+
     |   Base vmt    |
     |---------------|
b -> | Interface vmt |
     |---------------|
     |  Derived vmt  |
     +---------------+

So calling b->func2() uses Interface 's vtable, jumping to the 2nd entry which is Derived::func2() , as expected.

When a is declared as void* , it points at the Derived portion of the Derived object:

     +---------------+
     |   Base vmt    |
     |---------------|
     | Interface vmt |
     |---------------|
a -> |  Derived vmt  |
     +---------------+

But type-casting a to Interface* is Undefined Behavior , because a is not pointing at the Interface portion of the Derived object, resulting in an Interface* pointer to the Derived portion of the Derived object:

     +---------------+
     |   Base vmt    |
     |---------------|
     | Interface vmt |
     |---------------|
b -> |  Derived vmt  |
     +---------------+

So calling b->func2() mistakenly uses Derived 's vtable instead of Interface 's vtable, jumping to the 2nd entry which is Derived::func1() , unexpectedly.

Behaviour is undefined, since you are not converting a void * back to the actual type.

You either need to do

 void *a = (Interface*)(new Derived());
 auto b = (Interface *)a;

or add an extra step between your two

void* a = new Derived();     
auto temp = (Derived *)a;     // explicitly convert the void pointer to the actual type
auto b = (Interface*)temp;

which is equivalent to

void* a = new Derived();     
auto b = (Interface*)((Derived *)a);

Either way, without the conversion of the void * to Derived * before converting to Interface * , the behaviour is undefined.

Consider using one of the _cast s (eg static_cast ) for conversion instead of C-style casts as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM