虚拟析构函数是继承的吗?

时间:2010-02-04 09:00:55

标签: c++ inheritance virtual-destructor

如果我有一个带有虚析构函数的基类。是否有派生类来声明虚拟析构函数?

class base {
public:
    virtual ~base () {}
};

class derived : base {
public:
    virtual ~derived () {} // 1)
    ~derived () {}  // 2)
};

具体问题:

  1. 1)和2)相同吗?是2)因其基础而自动虚拟还是“停止”虚拟化?
  2. 如果没有任何关系,可以省略派生的析构函数吗?
  3. 声明派生析构函数的最佳做法是什么?如果可能,将其声明为虚拟,非虚拟或省略?

5 个答案:

答案 0 :(得分:89)

  1. 是的,他们是一样的。派生类没有声明虚拟的东西并不能阻止它成为虚拟的。事实上,如果它在基类中是虚拟的,那么就无法阻止任何方法(包含析构函数)在派生类中是虚拟的。在> = C ++ 11中,您可以使用final来防止它在派生类中被覆盖,但这并不妨碍它成为虚拟类。
  2. 是的,如果没有任何关系,派生类中的析构函数可以省略。它是否虚拟并不重要。
  3. 如果可能,我会省略它。为了清楚起见,我总是再次使用virtual关键字作为派生类中的虚函数。人们不应该一直在继承层次结构中找出函数是虚拟的。此外,如果您的类是可复制的或可移动的而无需声明自己的副本或移动构造函数,则声明任何类型的析构函数(即使您将其定义为default)将强制您声明副本并移动构造函数和赋值运算符,如果你想要它们,因为编译器将不再为你提供它们。
  4. 作为第3项的一个小点。在评论中已经指出,如果析构函数未声明,则编译器会生成默认值(仍然是虚拟的)。而默认的是内联函数。

    内联函数可能会将更多程序暴露给程序其他部分的更改,并使共享库的二进制兼容性变得棘手。此外,增加的耦合可能导致在某些类型的变化面前进行大量重新编译。例如,如果您确定确实需要虚拟析构函数的实现,那么调用它的每一段代码都需要重新编译。然而,如果您已在类主体中声明它,然后在.cpp文件中将其定义为空,那么您可以在不重新编译的情况下更改它。

    我个人的选择仍然是在可能的情况下省略它。在我看来,它使代码变得混乱,并且编译器有时可以使用默认实现而不是空代码执行稍微更有效的操作。但是你可能会遇到一些限制,这使得这个选择很糟糕。

答案 1 :(得分:1)

虚拟成员函数将隐含地隐藏此函数虚拟的任何重载。

因此1)中的虚拟是“可选的”,虚拟的基类析构函数也使所有子析构函数都是虚拟的。

答案 2 :(得分:1)

  1. 与所有方法一样,析构函数是自动虚拟的。你不能阻止一个方法在C ++中虚拟化(如果它已经被声明为虚拟,也就是说,在Java中没有相当于'final')
  2. 是的,可以省略。
  3. 如果我打算将这个类设置为子类,我会声明一个虚拟析构函数,无论它是否继承了另一个类,我也更喜欢将方法声明为虚拟,即使它不需要。如果您决定删除继承,这将使子类保持工作。但我认为这只是一种风格问题。

答案 3 :(得分:0)

1 /是 2 /是的,它将由编译器生成 3 /宣布虚拟与否之间的选择应该遵循你的惯例来覆盖虚拟成员 - 恕我直言,两种方式都有好的论据,只需选择一个并遵循它。

如果可能的话我会省略它,但是有一件事可能会引发你的声明:如果你使用编译器生成的那个,它是隐式内联的。有时候你想要避免使用内联成员(例如动态库)。

答案 4 :(得分:0)

虚拟函数被隐式覆盖。当子类的方法与基类中的虚函数的方法签名匹配时,它将被覆盖。 这很容易造成混淆,并且有可能在重构期间中断,因此自C ++ 11起存在overridefinal关键字以明确标记此行为。有相应的警告禁止静默行为,例如GCC中的-Wsuggest-override

SO上的overridefinal关键字存在一个相关问题:Is the 'override' keyword just a check for a overridden virtual method?

以及cpp参考https://en.cppreference.com/w/cpp/language/override中的文档

是否将override关键字与析构函数一起使用仍存在争议。例如,请参见与此相关的SO问题中的讨论:default override of virtual destructor 问题在于,虚拟析构函数的语义与常规函数不同。析构函数是链接在一起的,因此所有基类的析构函数都是在子代1之后调用的。但是,在使用常规方法的情况下,默认情况下不会调用重写方法的基础实现。可以在需要时手动调用它们。