3

我最近被问到一个基本的 C++ 问题,我实际上并不知道答案,而且我从未发现答案是什么。这是问题:

运行此程序时,可能会导致错误。您会对 Base 类进行哪些更改来修复它?

#include <iostream>

class Base {
public:
    virtual void ShowMessage () {
        std::cout << "Base class message\n";
    }

    // Something should be added here!
};

class Derived : public Base {
public:
    explicit Derived (const std::string & value)
        : myValue(value)
    {
    }

    virtual void ShowMessage () {
        std::cout << "Derived class message " << myValue << "\n";
    }

private:
    std::string myValue;
};

int main () {
    Base * obj = new Derived("Test message");
    obj->ShowMessage();
    delete obj;
}

它在我的机器上编译并运行得很好。有人知道他们在找什么吗?

4

3 回答 3

7

您需要向 Base 类添加一个虚拟析构函数。

class Base {
public:
    virtual void ShowMessage () {
        std::cout << "Base class message\n";
    }

    virtual ~Base() {}
};

在您的情况下, Derived::myvalue 将无法正确释放。

于 2013-06-24T04:47:44.613 回答
4

一个虚拟的析构函数。当这段代码执行...

delete obj;

std::string...除非以多态方式调用析构函数(即通过虚拟调度),否则它不会知道有一个要销毁/释放的。那是因为没有虚拟分派,基类析构函数运行,它没有那个std::string数据成员需要担心,所以不包含与之相关的代码。

准则很简单:无论何时使用指向基类的指针删除对象,都应确保基类具有虚拟析构函数。

其他任何东西都有上述问题,并且在技术上是未定义的行为。虽然尝试对未定义的行为进行“推理”并不是一个好主意,因为您无法确定发生的任何事情都不会比预期的更糟(如果不是现在,那么在某些未来的编译器/编译器版本上,或使用不同的编译开关或周围的代码,另一个 CPU 等),内存泄漏在这种情况下是一个明显的可能后果:可以预期 std::string 构造函数具有动态new-ed 一个字符数组,如果运行,讲师会删除它。不过,可能还有其他问题——如果派生类的数据成员是共享指针,那么如果有文件句柄、共享内存句柄、线程、锁等,那么指向的对象可能不会被正确地破坏/释放未能调用析构函数的后果可能很严重:数据可能不会被刷新到预期的目的地,应用程序可能会在反复泄漏后耗尽资源,或者当其他一些代码尝试获取它仍然持有的锁时,它可能会挂起。 ...

于 2013-06-24T04:48:09.727 回答
1

您必须将析构函数设为虚拟,因为在没有虚拟析构函数的情况下删除多态类型会导致未定义的行为:

根据 5.3.5.3:

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

于 2013-06-24T04:56:42.327 回答