为什么以下程序崩溃?我有一个基类,其析构函数不是虚拟的,但子类析构函数是虚拟的:
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base::Base CTOR " << std::endl;
}
~Base() {
std::cout << "Base::Base DTOR " << std::endl;
}
};
class Child : public Base {
public:
Child(){
std::cout << "Child::Child CTOR " << std::endl;
}
virtual ~Child() {
std::cout << "Child::Child DTOR " << std::endl;
}
};
int main (int argc, char **argv) {
Base *ptr = new Child;
delete ptr;
}
答案 0 :(得分:7)
您所观察到的被称为“未定义的行为”。如果您想通过Base
指针在Child
实例上调用删除,请将Base
的dtor设为虚拟。
从2003年标准,5.3.5 / 3:
在第一个替代(删除对象)中,如果是静态类型的 操作数与其动态类型不同,静态类型应为a 操作数的动态类型的基类和静态类型 有一个虚拟析构函数或行为未定义。
答案 1 :(得分:5)
您有未定义的行为,因为要删除的指针操作数的静态类型与它指向的对象的动态类型不匹配,并且您不满足允许将指针传递给此规则的例外的要求要删除的对象的基类,因为此异常要求 base 类具有虚拟析构函数。
任何行为都是可能的,包括“按预期”运行的代码或崩溃。
答案 2 :(得分:1)
希望这个例子可以帮助你明白这一点:
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base::Base CTOR " << std::endl;
}
~Base() {
std::cout << "Base::Base DTOR " << std::endl;
}
private:
protected:
};
class Child : public Base {
public:
Child(){
std::cout << "Child::Child CTOR " << std::endl;
}
~Child(){
std::cout << "Child::Child DTOR " << std::endl;
}
private:
protected:
};
class gChild : public Child {
public:
gChild(){
std::cout << "Child::Child gCTOR " << std::endl;
}
~gChild(){
std::cout << "Child::Child gDTOR " << std::endl;
}
private:
protected:
};
int main ( int argc, char **argv) {
Base *ptr = new gChild;
delete ptr;
}
如果是虚拟~Base(),则打印所有析构函数的打印。
如果是virtual〜child()或virtual~gChild(),则只打印基础析构函数。
这是因为析构函数以相反的方向执行。这里的行为是未定义的。您必须定义基本析构函数virtual以获得预期的结果。
感谢。
答案 3 :(得分:1)
看看这个:
#include <iostream>
class Base
{
public:
void nonvirtualmethod()
{ std::cout << "Base nonvirtualmethod" << std::endl; }
virtual void virtualmethod()
{ std::cout << "Base virtualmethod" << std::endl; }
};
class Derived: public Base
{
public:
void nonvirtualmethod()
{ std::cout << "Derived nonvirtualmethod" << std::endl; }
virtual void virtualmethod()
{ std::cout << "Derived virtualmethod" << std::endl; }
};
int main()
{
Derived d;
Derived* pd = &d;
Base* pb = &d; //< NOTE: both pd and pb point to the same object
pd->nonvirtualmethod();
pb->nonvirtualmethod();
pd->virtualmethod();
pb->virtualmethod();
}
我给你以下输出:
Derived nonvirtualmethod
Base nonvirtualmethod
Derived virtualmethod
Derived virtualmethod //< invoked by a Base*
这是因为pb
指针的静态类型(Base*
)之间存在差异
以及它指向的动态类型(Derived
)。
virtual和plain方法之间的区别在于非虚方法遵循静态类型映射(因此Base
指针调用Base::
方法),而虚方法遵循运行时类型链,因此如果如果Base*
指向Derived
,则会调用Derived
方法。
在这个意义上,析构函数并不特别:如果它不是虚拟的,Base
指针将不会调用Derived
指针,因此您将留下一个半毁坏的对象,即回到记忆库。
这就是UB(而不是简单否认)的原因是因为“内存存储”不是由语言本身管理,而是来自程序所在的平台:崩溃最有可能取决于这样的事实:缺少Derived
部分(仍然存活)将导致操作系统尝试释放具有错误起始地址的内存块。