0

我在C++ 测验中遇到了这个问题(初学者 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;
}

我给出了这个答案,结果是错误的 ==> 整数数组将不会被正确删除。

正确答案 ==> 行为未定义。

4

4 回答 4

9

您的基类的析构函数不是virtual. 这只是语言的一条规则,如果您通过指向基子对象的指针删除对象,则相应的基类必须具有虚拟析构函数,否则它是未定义的行为。

(实际上,如果您的基类没有虚拟析构函数,编译器可能不会发出必要的代码来对派生对象执行所有必要的清理。它只会假设您的对象与指针而不必费心去进一步查看,因为实际上对最派生对象的多态查找是以您不想不必要地强加的代价。)

§5.3.5/3:

在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义

于 2012-08-09T22:04:08.383 回答
2

您应该在基类中使您的析构函数成为虚拟的。现在代码的问题是delete p_base会导致基类的析构函数被调用。派生类中的那个不会被调用,分配给整数数组的内存也不会被释放。

发生这种情况是因为如果基类中的方法不是虚拟的,编译器只会查看指针类型并调用位于该类型中的方法(在这种情况下 - 它是基类),即决定调用什么方法编译时间基于指针的类型,而不是指针所指对象的真实类型。

于 2012-08-09T22:06:00.367 回答
0

出于好奇,我检查了 C++ 规范。问题的答案是第 5.3.5 节中的第 3 项:

在第一种选择(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义。

就个人而言,我会以与您相同的方式回答。如果忽略编译器的警告,最可能的结果是派生类的析构函数不会被调用。

于 2012-08-09T22:52:41.580 回答
-3

我猜编译器被允许优化这段代码,因此 p_derived 到 p_base 的分配永远不会发生。

更具体地说,编译器可以将代码优化为一行。

删除新的派生();

因此,人们认为像这样未定义的行为可以改变编译器真正优化代码的方式。

于 2012-08-09T22:06:25.860 回答