1

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

#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()

4

3 回答 3

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

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

如果您想将 to 转换为void*Interface*则需要将 an 存储Interface*void*开头,例如:

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

virtual通过对象指针调用方法时,编译器取消引用对象指针以访问指向属于对象指针指向的类型的 vtable 的隐藏指针,然后它索引到该 vtable 以知道要调用哪个类方法.

Derived对象在内存中看起来像这样(细节可能因编译器而异,但这是它的要点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对象由 中的所有内容组成BaseInterfaceDerived拼凑在一起。每个类仍然有自己的指向属于该类的虚拟方法表的指针。但是由于Derived实现了所有的virtual方法,一个Derived对象有多个 vtable 指针,它们都引用Derived的方法。

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

a被声明为Interface*时,它指向对象的Interface一部分Derived

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

类型转换aInterface*实际上是一个无操作,导致b成为Interface*指向对象Interface部分的指针Derived

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

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

a被声明为Base*时,它指向对象的Base一部分Derived

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

但是类型转换aUndefined Behavior,因为and彼此不相关,导致Interface*成为指向对象部分的指针:BaseInterfacebInterface*BaseDerived

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

所以调用b->func2()错误地使用Base了 's vtable 而不是's vtable ,意外地Interface跳转到了第二个条目。Derived::func1()

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

a被声明为Derived*时,它指向对象的Derived一部分Derived

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

类型转换atoInterface*是明确定义的,因为Interface它是 的基础Derived,因此bInterface*指向对象Interface部分的指针Derived

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

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

a被声明为void*时,它指向对象的Derived一部分Derived

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

但是类型转换aUndefined Behavior,因为Interface*没有指向对象的部分,导致指向对象部分的指针:aInterfaceDerivedInterface*DerivedDerived

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

所以调用b->func2()错误地使用Derived了 's vtable 而不是's vtable ,意外地Interface跳转到了第二个条目。Derived::func1()

于 2021-03-26T01:56:28.010 回答
3
void* a = new Derived();      // derived func1 called
auto b = (Interface*)a;

这是未定义的行为。将指向派生类的指针转换为指向其基类的指针的通常语义仅适用于将指向派生类的指针转换为指向其基类的指针时。但这不是这里发生的事情。

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

于 2021-03-26T01:55:09.727 回答
0

行为未定义,因为您没有将 a 转换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);

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

考虑使用其中一个_casts(例如static_cast)来代替 C 风格的强制转换。

于 2021-03-26T02:04:33.340 回答