class A{public:A() {} //default A::destructorprivate: ... //members};class B : public A{public:B(){} //default B::destructorprivate: ... //members};
有如上类定义A B,则delete A指针接收的B数组行为是未定义的:
A* a = new B[2];delete [] a;
The C++ Standard, [expr.delete], paragraph 3 [ISO/IEC 14882-2014], states the following:
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、In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
原因是此行为会被扩展为如下形式:
//C++数组中的元素逆序销毁(C++ Primer p425)for(int i = len - 1; i >= 0; --i){ A* pa = array + i * length(A); call pa->~A();}call A::operator delete[]
可见,指针的偏移是由A类型(静态类型)的长度计算得出,所以析构时会出现错误。VC++环境下会出现crash。
但实践中如果给A声明虚析构函数,并声明B的析构函数,则在VC++编译环境下不会出现crash,这是为什么呢?
查看汇编代码得知,当编译器检测到虚析构函数被重载(仅有虚析构还不行,必须有派生类重载)的情况下,会给基类产生一个虚函数vector deleteing destructor,派生类会重载这个虚函数:
class A{public:A() {} virtual ~A(){}public: virtual vector deleting destructor(...);private: ... //members};class B : public A{public:B(){} ~B()override{}public: vector deleting destructor(...)override;private: ... //members};
此函数的作用是实现正确的类型数组的析构操作,也就是对应B类型来说,可以生成如下代码:
void vector deleting destructor(...){ for(int i = len - 1; i >= 0; --i){ B* pb = array + i * length(B); call pb->~B(); }}
这样可以正确的计算偏移以析构每一个对象。
此情景下的的B类型虚表: