Are virtual destructors inherited?
如果我有一个带有虚拟析构函数的基类。有派生类也要声明虚析构函数吗?
1 2 3 4 5 6 7 8 9 10
| class base {
public:
virtual ~base () {}
};
class derived : base {
public:
virtual ~derived () {} // 1)
~derived () {} // 2)
}; |
具体问题:
1)和2)是否相同?2)自动虚拟是因为它的基础还是它"停止"了虚拟?
如果派生的析构函数与之无关,那么它可以被省略吗?
声明派生析构函数的最佳实践是什么?声明它是虚拟的、非虚拟的还是尽可能省略它?
是的,它们是一样的。不声明某个虚拟的派生类不会阻止它成为虚拟的。事实上,如果任何方法(包括析构函数)在基类中是虚拟的,就无法阻止它在派生类中是虚拟的。在>=C++ 11中,您可以使用EDCOX1 OR 0来防止它在派生类中被重写,但这并不妨碍它是虚拟的。
是的,如果派生类中的析构函数与之无关,则可以省略它。不管它是不是虚拟的。
如果可能的话,我会省略它。为了清晰起见,我总是再次为派生类中的虚拟函数使用virtual关键字。人们不必一直沿着继承层次结构往上走,就可以发现一个函数是虚拟的。此外,如果您的类是可复制的或可移动的,而不必声明自己的复制或移动构造函数,则声明任何类型的析构函数(即使您将其定义为default)都将强制您声明复制和移动构造函数和赋值运算符(如果您希望它们成为编译器不再为您放入的构造函数)。
作为第3项的一小部分。注释中指出,如果未声明析构函数,编译器将生成默认的析构函数(仍然是虚拟的)。默认的是一个内联函数。
内联函数可能会使更多程序暴露在程序其他部分的更改中,并使共享库的二进制兼容性变得棘手。此外,增加的耦合可能会导致在某些类型的变化面前进行大量的重新编译。例如,如果您决定您真的想要一个虚拟析构函数的实现,那么调用它的每一段代码都需要重新编译。但是,如果您已经在类体中声明了它,然后在.cpp文件中定义为空,那么您可以在不重新编译的情况下修改它。
我个人的选择仍然是尽可能地忽略它。在我看来,它会使代码混乱,编译器有时可以用默认实现而不是空的实现做一些稍微高效的事情。但你可能会受到一些限制,所以这是一个糟糕的选择。
- 你的最后一句话应该是"不应该"而不是"应该"。
- @克里斯·卢茨,我在这方面领先你。它现在已被编辑成提交文件。-)
- 我不同意"省略"部分。在头中声明它并在源中定义它(空体)并不需要花费太多。如果您这样做,您总是可以回来添加一些步骤(日志记录?)不必强迫客户机重新编译。
- @matthieu m,这是不声明任何内联函数的参数。尽管我会同意,想要记录构造函数和析构函数调用比任何其他调用都要常见一些。
- 实际上,我并没有在内联中声明太多函数,甚至连经典的"accessors"也没有,但是在一家大公司中工作时,我们可能有比大多数公司都高的二进制兼容性约束。
- 如果子类中的函数在基类中隐藏同名但参数不同的函数,该子类函数是否仍然是虚拟的?@ MatthieuM。
- 我刚刚从这次谈话中了解到,声明虚拟析构函数实际上会导致类变得不可移动!因此,无论何时声明虚拟析构函数,如果需要这些属性,还必须提供整个规则5。如果可能的话,还有更多的理由可以忽略。
- 这个答案一般需要一个次要的C++ 11更新。
与所有方法一样,析构函数是自动虚拟的。你不能阻止一个方法在C++中是虚拟的(如果它已经被声明为虚拟的,也就是说,Java中没有等价的"最终")。
是的,可以省略。
如果我希望这个类被子类化,我会声明一个虚拟析构函数,不管它是否子类化另一个类,我也更喜欢继续声明方法virtual,即使它不是必需的。如果您决定删除继承,这将使子类保持工作状态。但我想这只是风格问题。
- 析构函数不是自动虚拟的,也不是任何其他成员函数。
- @当然不是,我指的是示例中的析构函数(即基类有一个虚拟的析构函数),而不是一般的析构函数。这对所有方法都是正确的,不仅仅是析构函数。
- 自从C++ 11以来,我们有EDCOX1,0。
虚成员函数将隐式地使该函数的任何重载都是虚的。
所以1)中的virtual是"可选的",基类析构函数为virtual会使所有子析构函数也为virtual。
1 /是2/是的,它将由编译器生成3/在声明它是否为虚拟成员之间的选择应该遵循您对覆盖虚拟成员的约定——imho,这两种方式都有很好的论点,只需选择一个并遵循它。
如果可能的话,我会省略它,但是有一件事可能会鼓励您声明它:如果您使用编译器生成的,它是隐式内联的。有时您希望避免内联成员(例如动态库)。