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().但是,仍有两个问题:
为什么虚拟析构函数是必需的,因为我们在这些类中没有任何数据?
为什么pa1和pa2的行为不同?我发现这与类放在父列表中的顺序有关.如果您将其更改为:
class B : public A2, public A1
然后
delete pa1;
会导致崩溃.
一种可能的典型内存布局:
+-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的回答).
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)