虚拟析构函数是否被继承?


76

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

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

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

具体问题:

  1. 1)和2)是否相同?2)是因为其基础而自动虚拟还是“停止”了虚拟性?
  2. 如果无关的派生析构函数可以省略吗?
  3. 声明派生的析构函数的最佳实践是什么?声明它是虚拟的,非虚拟的还是尽可能省略它?

Answers:


91
  1. 是的,它们是相同的。派生类未声明虚拟对象并不能阻止其成为虚拟对象。实际上,如果方法在基类中是虚拟的,则无法阻止任何方法(包括析构函数)在派生类中是虚拟的。在> = C ++ 11中,可以使用final它来防止它在派生类中被覆盖,但这并不能防止它是虚拟的。
  2. 是的,如果派生类中的析构函数没有任何关系,则可以省略。它的虚拟与否无关紧要。
  3. 如果可能的话,我会忽略它。virtual为了清楚起见,我总是将关键字再次用于派生类中的虚函数。人们不必不必一直沿继承层次结构去弄清楚一个函数是虚拟的。此外,如果您的类是可复制的或可移动的,而不必声明自己的副本或移动构造函数,则声明任何类型的析构函数(即使您将其定义为default)也将强制您声明副本并移动构造函数和赋值运算符(如果需要)它们,因为编译器将不再为您提供它们。

作为第3项的一点。在注释中已经指出,如果未声明析构函数,则编译器将生成默认的(仍然是虚拟的)。那个默认值是一个内联函数。

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

我个人的选择仍然是在可能的情况下忽略它。在我看来,它会使代码混乱,而且编译器有时可以通过默认实现比空的实现更有效的工作。但是您可能会受到一些限制,因此这是一个糟糕的选择。


1
我不同意“省略”部分。在标头中声明它并在源代码中定义它(空主体)并不会花费太多。如果这样做,您总是可以返回并添加一些步骤(登录?),而不必强制客户端重新编译。
Matthieu M.

1
实际上,我没有声明很多内联函数,甚至没有经典的“访问器”,但是在一家大公司工作时,我们的二进制兼容性约束可能比大多数都更高。
Matthieu M.

3
我刚刚从这次谈话中得知,声明虚拟析构函数实际上将导致您的类无法移动!因此,无论何时声明虚拟析构函数,都必须提供完整的规则5(如果要使用这些属性)。甚至有更多的原因在可能的情况下省略。
尼尔·特拉夫特

1
“此外,如果您的类是可复制的或可移动的,而不必声明自己的副本或移动构造函数,则声明任何类型的析构函数(即使您将其定义为默认值)也将强制您声明副本并移动构造函数和赋值运算符,如果您需要它们,因为编译器将不再为您提供它们。” 那是错的!zh.cppreference.com/w/cpp/language/copy_constructor
Kaiserludi

1
@Kaiserludi-我将再次确认这是真的,并修复我的答案。
全方位

2
  1. 与所有方法一样,析构函数是自动虚拟的。您无法阻止方法在C ++中被虚拟化(如果该方法已经被声明为虚拟方法,即Java中没有“最终”方法)
  2. 是的,可以省略。
  3. 如果我打算将此类作为子类,则我将声明一个虚拟析构函数,无论是否将其子类化为另一个类,即使不需要它,我也更喜欢继续声明虚拟方法。如果您决定删除继承,这将使子类保持工作状态。但是我想这只是样式问题。

析构函数不是自动虚拟的,其他成员函数也不是。

1
@尼尔; 当然不是,我在示例中指的析构函数(即基类具有虚拟实例),而不是通常的析构函数。对于所有方法,不仅是析构函数,都是如此。
falstro,2010年

1
从C ++ 11开始,我们有了final
whoan

1

虚拟成员函数将隐式地使该函数的任何重载变为虚拟。

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


0

1 /是2 /是,它将由编译器生成。3 /在声明是否为虚拟对象之间进行选择应遵循您对覆盖的虚拟成员的约定-恕我直言,两种方法都有很好的论点,只需选择一个并遵循即可。

如果可能的话,我会忽略它,但是有一件事可能会促使您声明它:如果使用编译器生成的代码,它就是隐式内联的。有时您想避免使用内联成员(例如,动态库)。


0

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

还有一个相关的问题override,并final在SO关键字:是“越权”的关键字只是检查了重写的虚方法?

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.