繁体   English   中英

多个 inheritance 订单重要吗?

[英]Does multiple inheritance order matters?

我想出了一些令人困惑的情况。 下面是他们。

#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();
    
    ...
}

执行b->func2()时,结果因变量 a 的显式类型而异。

结果在评论中。

为什么在执行b->func2()时它们会有所不同?

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

这是未定义的行为。 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. 但这不是这里发生的事情。

到其他指针的中间转换,如void *会使所有保修失效(双关语不是有意的)。

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

这是未定义的行为 您没有将void*转换回存储在其中的相同类型( Derived* )。

如果要将void*转换为Interface* ,则需要将Interface*存储在void*中,例如:

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称呼。

Derived的 object 在 memory 中看起来像这样(细节可能因编译器而异,但这是它的要点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()
                       +------------+

Derived object 由BaseInterfaceDerived拼凑在一起的所有内容组成。 每个 class 仍然有自己的指向属于该 class 的虚拟方法表的指针。 但是由于Derived实现了所有的virtual方法,一个Derived object 有多个 vtable 指针,它们都引用了Derived的方法。

1:为了提高效率, Derived很可能只有 1 个 vtable,并且它的所有 3 个 vmt 指针都指向该单个表的不同区域。 但这是一个实现细节,对于这个答案来说并不重要。

a被声明为Interface*时,它指向Derived object 的Interface部分:

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

a类型转换为Interface*实际上是一个无操作,导致b是一个Interface*指针,指向Derived object 的Interface部分:

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

因此调用b->func2()使用Interface'的 vtable,如预期的那样跳转到Derived::func2()的第二个条目。

a被声明为Base*时,它指向Derived object 的Base部分:

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

但是将a类型转换为Interface*Undefined Behavior ,因为BaseInterface彼此不相关,导致bInterface*指向Derived object 的Base部分的指针:

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

因此调用b->func2()错误地使用Base的 vtable 而不是Interface的 vtable,意外地跳转到第二个条目Derived::func1()

如果你在这里使用static_cast而不是 C 风格的转换,编译就会失败(这就是为什么你应该避免在 C++ 中使用 C 风格的转换。)。

a被声明为Derived*时,它指向Derived object 的Derived部分:

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

a类型转换为Interface*是明确定义的,因为InterfaceDerived的基础,导致b是指向Derived object 的Interface部分的Interface*指针:

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

因此调用b->func2()使用Interface的 vtable,如预期的那样跳转到Derived::func2()的第二个条目。

a被声明为void*时,它指向Derived object 的Derived部分:

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

但是将a类型转换为Interface*Undefined Behavior ,因为a没有指向Derived object 的Interface部分,从而导致Interface*指针指向Derived object 的Derived部分:

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

因此调用b->func2()错误地使用Derived的 vtable 而不是Interface的 vtable,意外地跳转到第二个条目Derived::func1()

行为未定义,因为您没有将void *转换回实际类型。

你要么需要做

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

或者在你们两个之间增加一个额外的步骤

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

这相当于

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

无论哪种方式,如果在转换为Interface *之前没有将void *转换为Derived * ,则行为是未定义的。

考虑使用_cast之一(例如static_cast )进行转换,而不是使用 C 风格的强制转换。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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