【问题标题】:Multiple inheritance and polymorphism in C++C++中的多重继承和多态性
【发布时间】:2015-10-26 02:16:28
【问题描述】:
考虑以下代码:
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()。但是,还有两个问题:
为什么需要虚拟析构函数,因为我们在这些类中都没有任何数据?
为什么 pa1 和 pa2 的行为不同?我发现这与类在父列表中的放置顺序有关。如果您将其更改为:
class B : public A2, public A1
然后
delete pa1;
会导致崩溃。
【问题讨论】:
标签:
c++
inheritance
polymorphism
【解决方案1】:
一种可能的典型内存布局:
+-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
|新B
当您现在delete p; 时,这可能会导致免费存储释放器出现问题,因为存储在p 中的地址与您从分配器(new B)收到的地址不同。如果您转换为A1,即A1* p = new B,则不会发生这种情况,因为在这种情况下没有偏移量。
Live example
您可以通过dynamic_cast 恢复原始指针来避免尝试避免此特定问题:
delete dynamic_cast<void*>(p);
Live example
但是不要依赖这个。它仍然是未定义的行为(参见 Barry 的回答)。
【解决方案2】:
来自[expr.delete]:
在第一种选择(删除对象)中,如果要删除的对象的静态类型与其不同
动态类型,静态类型应该是要删除的对象的动态类型的基类,
静态类型应具有虚拟析构函数或行为未定义。
未定义的行为是未定义的。虚拟析构函数是必要的,因为标准是这样说的(另见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;
^
【解决方案3】:
顺序有点相关,因为析构函数的顺序与声明顺序相反。然而,它甚至适用于 pa1 实际上是“幸运的”,因为使用非虚拟析构函数删除抽象类类型的对象会导致未定义的行为。总是需要为抽象类添加一个虚拟析构函数。