C++中的多重继承和多态

Pio*_*r G 2 c++ polymorphism inheritance

请考虑以下代码:

class A1
{
    virtual void a() = 0;
};

class A2
{
    virtual int a(int x) = 0;
};

class B : public A1, public A2
{
    void a() {}
    int  a(int x) { return x; }
};


int main()
{
    A1* pa1;
    pa1 = new B;
    delete pa1;

    A2* pa2;
    pa2 = new B;
    delete pa2;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

类A1和A2只是纯抽象的,因此多重继承应该没有坏处.现在,上面的代码将在析构函数调用期间导致崩溃,但是什么是特殊的,仅针对一个对象:pa2.解决这个问题似乎非常明显 - 使用虚拟析构函数~A1()和~A2().但是,仍有两个问题:

  1. 为什么虚拟析构函数是必需的,因为我们在这些类中没有任何数据?

  2. 为什么pa1和pa2的行为不同?我发现这与类放在父列表中的顺序有关.如果您将其更改为:

class B : public A2, public A1

然后

delete pa1;

会导致崩溃.

dyp*_*dyp 8

一种可能的典型内存布局:

+-A1---+
| vptr |
+------+

+-A2---+
| vptr |
+------+

+-B------------------+
| +-A1---+  +-A2---+ |
| | vptr |  | vptr | |
| +------+  +------+ |
+--------------------+

vptr是指向一些有关最派生类型的信息的指针,例如虚函数表,RTTI等(参见例如Itanium C++ ABI vtable布局)

所以,当你写作时A2* p = new B,你最终会得到:

+-B------------------+
| +-A1---+  +-A2---+ |
| | vptr |  | vptr | |
| +------+  +------+ |
+-----------^--------+
^           | p
| new B

现在delete p;,这可能会导致免费存储解除p分配器出现问题,因为存储的地址与您从allocator(new B)收到的地址不同.如果你施放A1,即不会发生A1* p = new B这种情况,因为在这种情况下没有偏移.

实例

您可以通过以下方法恢复原始指针,以避免尝试避免此特定问题dynamic_cast:

delete dynamic_cast<void*>(p);
Run Code Online (Sandbox Code Playgroud)

实例

不要依赖于此.它仍然是未定义的行为(见Barry的回答).


Bar*_*rry 7

From [expr.delete]:

在第一个备选(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,静态类型应具有虚拟析构函数或行为未定义.

未定义的行为未定义.虚拟析构函数是必要的,因为标准是这样说的(参见dyp的答案)

使用警告进行编译也有助于:

main.cpp: In function 'int main()':
main.cpp:22:12: warning: deleting object of abstract class type 'A1' which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
     delete pa1;
            ^
main.cpp:26:12: warning: deleting object of abstract class type 'A2' which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
     delete pa2;
            ^
Run Code Online (Sandbox Code Playgroud)