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

时间:2015-08-03 20:38:00

标签: c++ inheritance polymorphism

请考虑以下代码:

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;
}

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

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

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

  3. class B : public A2, public A1

    然后

    delete pa1;

    会导致崩溃。

3 个答案:

答案 0 :(得分:8)

可能的典型内存布局:

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

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

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

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

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

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

当你现在delete p;时,这可能会导致免费存储解除分配器出现问题,因为p中存储的地址与您从分配器接收的地址不同({{1 }})。如果您转向new B,即A1,则不会发生这种情况,因为在这种情况下没有偏移。

Live example

您可以通过A1* p = new B恢复原始指针,避免尝试避免此特定问题:

dynamic_cast

Live example

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

答案 1 :(得分:7)

来自[expr.delete]:

  

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

未定义未定义的行为。虚拟析构函数是必要的,因为标准是这样说的(参见dyp's answer

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

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;
            ^

答案 2 :(得分:0)

订单是相关的,因为析构函数的顺序与声明顺序相反。但是,它实际上是“幸运的”它甚至适用于pa1,因为使用非虚拟析构函数删除abstact类类型的对象会导致未定义的行为。总是需要为抽象类添加虚拟析构函数。