【问题标题】:Is it necessary to have virtual destructor if the derived class only contains automatic variable members?如果派生类只包含自动变量成员,是否需要虚拟析构函数?
【发布时间】:2019-10-11 18:52:18
【问题描述】:
struct base
{
    base(){}
    ~base() { cout << "base destructor" << endl; }
};

struct derived : public base
{
    derived() : base() { vec.resize(200000000); }
    ~derived() { cout << "derived destructor" << endl; }
    vector<int> vec;
};

int main()
{
    base* ptr = new derived();
    delete ptr;

    while (true)
    {

    }
}

由于删除操作没有调用派生对象的析构函数导致上述代码泄漏。但是……

struct base
{
    base() {}
    ~base() { cout << "base destructor" << endl; }
};

struct derived : public base
{
    derived() : base() {}
    ~derived() { cout << "derived destructor" << endl; }
    int arr[200000000];
};

int main()
{
    base* ptr = new derived();
    delete ptr;

    while (true)
    {

    }
}

在第二种情况下,尽管只调用了基本析构函数,但内存不会泄漏。所以我假设如果我的所有成员都是自动变量,那么没有基本析构函数是安全的吗?当派生对象的析构函数未被调用时,派生类中的“arr”成员是否永远不会超出范围?幕后发生了什么?

【问题讨论】:

  • 这不是关于泄漏,而是关于未定义的行为。泄漏可能是 UB 的结果,但主要问题是它可能会做任何事情......
  • 所以我假设如果我的所有成员都是自动变量,那么没有基析构函数是安全的? -- 如果你的“安全”派生类有std::string 是会员吗?或者,如果派生类有任何类型的成员,该成员的析构函数需要执行某种类型的“工作”,即关闭句柄,该怎么办?

标签: c++ oop memory


【解决方案1】:

是的!

我看到您正在“实际地”考虑可能会错过哪些破坏。考虑到派生类的析构函数不仅仅是您编写的析构函数体——在这种情况下,您还需要考虑成员销毁,并且您的建议可能无法销毁向量(因为例程非虚拟销毁您的对象不会甚至知道有一个派生部分需要考虑)。该向量具有动态分配的内容,这些内容将被泄露。

但是,我们甚至不需要走那么远。您的程序的行为是未定义的,句号,故事的结尾。优化器可以根据您的代码是否有效做出假设。如果不是这样,您可以并且应该期待奇怪的事情发生,这可能不符合您对计算机的预期工作方式。那是因为 C++ 是一种抽象,编译很复杂,而且你与该语言签订了合同。

【讨论】:

【解决方案2】:

如果派生对象通过指向该基类的指针被删除,则始终需要在基类中具有虚拟析构函数。否则程序的行为是未定义的。在任何其他情况下,都不需要虚拟析构函数。类有哪些成员无关紧要。

【讨论】:

    【解决方案3】:

    没有必要在发生内存泄漏的情况下仍然调用 UB。如果您的派生类不是微不足道的,那么内存泄漏是一种预期的 UB。示例:

    #include <iostream>
    
    class  Field {
    public:
        int *data;
        Field() : data(new int[100]) {} 
        ~Field() { delete[] data; std::cout << "Field is destroyed"; }
    };
    
    class Base {
        int c;
    };
    
    // Derived class, contains a non-trivial non-static member                 
    class Core : public Base 
    {
        Field A;
    };
    
    int main()
    {
        Base *base = new Core;
        delete base;  // won't delete Field
    }
    

    他 C++ 标准,[expr.delete],第 3 段状态(2014 版)

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

    实际上,如果基类是微不足道的,那么所有字段都是微不足道的,派生类不包含非静态或非普通成员,有人可能会争辩说,这些类是平等的,但我还没有找到方法通过标准证明。可能是IB而不是UB。

    【讨论】:

      猜你喜欢
      • 2011-04-26
      • 2021-08-22
      • 1970-01-01
      • 2014-03-23
      • 2012-09-01
      • 1970-01-01
      • 2021-01-31
      • 2016-01-05
      • 2012-02-24
      相关资源
      最近更新 更多