我在C++ quiz中有这个问题(初学者C ++):我的答案不正确,我想理解正确答案背后的解释 - “未定义的行为”
问题: 函数foo()返回后会在以下代码中发生什么?
class base
{
public:
base() { }
~base() { }
};
class derived : public base
{
private:
int *p_pi_values;
public:
derived() : p_pi_values(new int[100]) { }
~derived() { delete [] p_pi_values; }
};
void foo(void)
{
derived *p_derived = new derived();
base *p_base = p_derived;
// Do some other stuff here.
delete p_base;
}
我给出了这个错误的答案==>整数数组将无法正确删除。
正确答案==>行为未定义。
答案 0 :(得分:9)
基类的析构函数不是virtual
。这只是一种语言规则,如果通过指向基础子对象的指针删除对象,则相应的基类必须具有虚拟析构函数,否则它是未定义的行为。
(实际上,如果您的基类没有虚拟析构函数,编译器可能不会发出必要的代码来对派生对象执行所有必要的清理。它只会假设您的对象属于与指针相同的类型并且不需要进一步查看,因为实际上大多数派生对象的多态查找是以您不希望不必要地施加的代价来进行的。)
<强>§5.3.5/ 3:强>
在第一个备选(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数的动态类型的基类,静态类型应具有虚拟析构函数或者行为未定义
答案 1 :(得分:2)
您应该在基类中使析构函数虚拟化。现在代码的问题是delete p_base
将导致调用基类的析构函数。将不会调用派生类中的一个,并且不会释放为整数数组分配的内存。
这是因为如果一个方法在基类中不是虚拟的,编译器只是查看一个指针类型并调用一个位于这种类型的方法(在这种情况下 - 它是一个基类),即决定调用什么方法在编译期间根据指针的类型而不是指针所指对象的实际类型进行。
答案 2 :(得分:0)
出于好奇,我检查了C ++规范。问题的答案是第5.3.5节中的第3项:
在第一个替代(删除对象)中,如果是静态类型 要删除的对象与其动态类型静态不同 type应该是对象的动态类型的基类 删除,静态类型应具有虚拟析构函数或 行为未定。
就我个人而言,我会以同样的方式回答。如果忽略编译器的警告,最可能的结果是派生类的析构函数不会被调用。
答案 3 :(得分:-3)
我想编译器可以优化这段代码,因此p_derived的赋值永远不会发生。
更具体地说,编译器可以将代码优化为一行。
删除new derived();
因此可以看出,未定义的行为可以改变编译器真正优化代码的方式。