简体   繁体   English

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

[英]Multiple inheritance and polymorphism in C++

Consider the following code: 请考虑以下代码:

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

Classes A1 and A2 are just pure abstract, so multiple inheritance should do no harm. 类A1和A2只是纯抽象的,因此多重继承应该没有坏处。 Now, the above code will cause a crash during destructor call, but what is peculiar, only for one object: pa2. 现在,上面的代码将在析构函数调用期间导致崩溃,但是什么是特殊的,仅针对一个对象:pa2。 The fix to this problem seems quite obvious - use virtual destructors ~A1() and ~A2(). 解决这个问题似乎非常明显 - 使用虚拟析构函数~A1()和~A2()。 However, there are still two questions: 但是,仍有两个问题:

  1. Why the virtual destructors are necessary, since we do not have any data in any of these classes? 为什么虚拟析构函数是必需的,因为我们在这些类中没有任何数据?

  2. Why is the behavior different for pa1 and pa2? 为什么pa1和pa2的行为不同? I have found that this is related to the order in which classes are placed on the parent list. 我发现这与类放在父列表中的顺序有关。 If you changed it to: 如果您将其更改为:

class B : public A2, public A1

then 然后

delete pa1;

would cause crash. 会导致崩溃。

A possible and typical memory layout: 一种可能的典型内存布局:

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

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

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

vptr is a pointer that points to some information about the most-derived type, eg the virtual function table, RTTI etc. (see eg the Itanium C++ ABI vtable layout ) vptr是一个指针,指向有关最派生类型的一些信息,例如虚函数表,RTTI等(参见例如Itanium C ++ ABI vtable布局

So, when you write A2* p = new B , you'll end up with: 所以,当你写A2* p = new B ,你最终得到:

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

When you now delete p; 当你现在delete p; , this can cause trouble in the free store deallocator, since the address stored in p is not the same as the address you've received from the allocator ( new B ). ,这可能会导致免费存储解除分配器出现问题,因为存储在p中的地址与您从分配器接收的地址( new B )不同。 This won't happen if you cast to A1 , ie A1* p = new B , since there's no offset in this case. 如果你转换为A1 ,即A1* p = new B ,则不会发生这种情况,因为在这种情况下没有偏移。

Live example 实例

You can avoid try to avoid this particular problem by restoring the original pointer via a dynamic_cast : 您可以通过dynamic_cast恢复原始指针,避免尝试避免此特定问题:

delete dynamic_cast<void*>(p);

Live example 实例

But do not rely on this . 不要依赖于此 It is still Undefined Behaviour (see Barry's answer). 它仍然是未定义的行为(见Barry的回答)。

From [expr.delete]: 来自[expr.delete]:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined . 在第一个备选(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,静态类型应具有虚拟析构函数或行为未定义

Undefined behavior is undefined. 未定义的行为未定义。 The virtual destructor is necessary because the standard says so (see also dyp's answer ) 虚拟析构函数是必要的,因为标准是这样说的(参见dyp的答案

Compiling with warnings also helps: 使用警告进行编译也有助于:

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

The order is sort of relevant because the order of destructors is opposite of the declaration order. 该订单是相关的,因为析构函数的顺序与声明顺序相反。 However, it is actually "lucky" that it even works for pa1, since deleting objects of abstact class type with non-virtual destructor causes undefined behaviour. 但是,它实际上是“幸运的”它甚至适用于pa1,因为使用非虚拟析构函数删除abstact类类型的对象会导致未定义的行为。 One always needs to add a virtual destructor for abstract classes. 总是需要为抽象类添加虚拟析构函数。

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

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