【问题标题】:Can the default destructor be generated as a virtual destructor automatically?默认析构函数可以自动生成为虚拟析构函数吗?
【发布时间】:2010-11-10 04:32:41
【问题描述】:

默认析构函数可以自动生成为虚拟析构函数吗?

如果我定义了一个基类但没有默认析构函数,是否有默认的虚拟析构函数 自动生成?

【问题讨论】:

  • 顺便问一下,什么是默认析构函数?还有不止一种析构函数吗?
  • @yossi1981:如果你没有在类中声明析构函数,那么编译器会为你插入一个析构函数。在某些不寻常的情况下可能会出错,这个“默认析构函数”与您定义的“~MyClass() {}”相同。
  • @onebyone: 准确地说是:public: ~MyClass() {} - 尽管默认情况下类成员是私有的。
  • 带有非虚拟析构函数的基类和带有虚拟析构函数的派生类感觉像是一个内置的设计缺陷。

标签: c++ destructor


【解决方案1】:

没有。使方法虚拟化是有成本的,而 C++ 的理念是不让您为未明确声明要使用的东西付费。如果虚拟析构函数会自动生成,那么您将自动付出代价。

为什么不直接定义一个空的虚拟析构函数?

【讨论】:

  • 请注意,从C++11 开始,您也可以使用virtual ~Foo() = default;(如果最近有人在阅读此内容)
  • 如果该类有任何其他虚函数,会产生什么额外成本?你能举个例子吗?
  • @Spencer:调用析构函数需要在运行时通过对象的vtable解析d-tor代码的地址。对于非虚拟析构函数,代码的地址在编译时是已知的。
  • @Marco 带有虚拟析构函数的基类和带有非虚拟析构函数的派生类感觉像是一个内置的设计缺陷。
  • @Spencer:如果基类声明了(隐式或显式)虚拟析构函数,则派生类的析构函数隐式声明为虚拟。如果您的基类有一个非虚拟析构函数,那么从它继承并添加新成员是很危险的,因为如果您使用基类类型的指针调用 delete,则永远不会调用这些成员的析构函数!
【解决方案2】:

在 C++ 11 中,您可以使用:

class MyClass
{
  // create a virtual, default destructor
  virtual ~MyClass() = default;
};

【讨论】:

  • 这编译使用icpc,但不是g++-4.6.3,并给出消息error: ˜virtual MyClass::~MyClass() declared virtual cannot be defaulted in the class body。看起来很明确,他们不希望您这样做;是否有适用于 g++ 的版本?
  • @NickThompson,我已经成功地将它与 gcc 4.8.1 和 4.9.0 一起使用。它seems that this is supported in 4.6。你是用-std=c++0x编译吗?
  • 这里使用default 与使用{} 的空构造函数相比有什么不同吗?
  • @Zitrax 我确定您指的是 destructor,而不是 constructor。请注意,对于构造函数, 是用户提供的空主体构造函数和默认构造函数(带有 = 默认值)之间的区别。不同之处在于前者阻止该类成为聚合类,而后者则不会。可以使用大括号语法初始化聚合类,这有时可能有用/需要。但是请注意,还有其他细节可能会阻止类成为聚合(最值得注意的是虚拟方法)。
  • 想一想,使用 =default 作为析构函数是有原因的,至少在它不是虚拟的情况下:它允许类不仅是一个聚合,而是一个普通旧数据 (POD) 结构(假设满足其他要求)。用户提供的空析构函数已经阻止您拥有 POD 结构。
【解决方案3】:

是的,通过从具有虚拟析构函数的基类继承。在这种情况下,您已经为多态类(例如 vtable)付出了代价。

【讨论】:

    【解决方案4】:

    不,所有析构函数默认不是虚拟的。

    您需要在所有基类上定义一个虚拟析构函数

    除此之外。

    引用 Scott Meyers 在他的《Effective C++》一书中的话:

    C++ 语言标准是 在这个话题上异常清晰。什么时候 您尝试删除派生类 通过基类指针对象 并且基类有一个非虚 析构函数(正如 EnemyTarget 所做的那样), 结果未定义

    在实践中,如果您认为有人最终可能会从中创建派生类,那么使用虚拟析构函数定义类通常是一个好主意。无论如何,我倾向于让所有类都有虚拟析构函数。是的,这是有相关成本的,但不更频繁地将其虚拟化的成本会导致运行时开销微不足道。

    我建议,仅当您完全确定自己想要它时才将其设为非虚拟,而不是依赖编译器强制执行的默认非虚拟。您可能不同意,但是(总而言之)我最近在一些遗留代码上发生了可怕的内存泄漏,我所做的只是将 std::vector 添加到已经存在多年的类之一中。事实证明,它的一个基类没有定义析构函数(默认析构函数是空的,非虚拟的!)并且在没有内存泄漏之前没有像这样分配内存。很多天的调查和后来浪费的时间......

    【讨论】:

    • 因此,如果我正确理解情况,您的代码在您进行更改之前已经有未定义的行为(尽管不是内存泄漏):通过基类指针删除,您的派生对象将被破坏没有(首先)被破坏本身。没有其他数据成员(需要销毁)不会定义行为。所以你的调查没有白费……
    【解决方案5】:

    Uri 和 Michael 是对的——我只想补充一点,如果你不得不触摸两个文件来声明和定义析构函数,那么在标题中定义一个最小的内联文件是完全可以的:

    class MyClass
    {
       // define basic destructor right here
       virtual ~MyClass(){}
    
       // but these functions can be defined in a different file
       void FuncA();
       int FuncB(int etc);
    }
    

    【讨论】:

    • 其实我想你会发现,当你链接这个时,你会得到一个未定义的对 MyClass' vtable 的引用。
    • 如果你使用 GCC 并且你没有定义 FuncA 和 FuncB 非内联,你只会得到一个“未定义的 vtable 引用”错误,这只是因为 GCC 未能发出所有允许正确链接的必要内容。
    • 我对此感到惊讶,为什么会导致链接问题?有人可以详细说明吗?
    • 我一直这样做,它在 MSVC 中链接得很好,更不用说 C++ 标准明确允许这样做了。我猜这是 GCC 中的错误。
    【解决方案6】:

    目前,Uri 是正确的。另一方面,在你的类中声明了一个虚方法之后,无论如何你都要为虚表的存在付出代价。事实上,如果你的类有虚方法但没有虚析构函数,编译器会警告你。这可能会成为自动生成默认虚拟析构函数的候选对象,而不是讨厌的警告。

    【讨论】:

      【解决方案7】:

      没有。您需要将其声明为虚拟。

      【讨论】:

        【解决方案8】:

        有人说它从不默认为虚拟。这并不完全正确。

        即使析构函数不是继承的,如果基类声明它的析构函数是虚拟的,派生的析构函数总是会覆盖它。

        这意味着如果基类有一个虚拟析构函数,您不必将派生类的析构函数定义为虚拟,也不必显式编写:

        /// All of these are unnecessary and won't change the destruction behavior
        /// as long as Class inherits from a base class with a virtual destructor
        virtual ~Class() = default;
        // or
        ~Class() override = default;
        // or
        ~Class() {}
        

        【讨论】:

          猜你喜欢
          • 2016-03-26
          • 2014-01-30
          • 2010-10-24
          • 2017-04-21
          • 1970-01-01
          • 2021-08-22
          • 2012-07-14
          • 2018-06-29
          • 2014-05-22
          相关资源
          最近更新 更多