虚拟析构函数是继承的吗?
如果我有一个带有虚拟析构函数的基类。派生类也有声明虚拟析构函数吗?
class base {
public:
virtual ~base () {}
};
class derived : base {
public:
virtual ~derived () {} // 1)
~derived () {} // 2)
};
具体问题:
- 1)和2)相同吗? 2)是因为它的基础而自动虚拟还是它“停止”虚拟性?
- 派生析构函数如果无关的话可以省略吗?
- 声明派生析构函数的最佳实践是什么?声明它是虚拟的、非虚拟的还是如果可能的话省略它?
If I have a base class with a virtual destructor. Has a derived class to declare a virtual destructor too?
class base {
public:
virtual ~base () {}
};
class derived : base {
public:
virtual ~derived () {} // 1)
~derived () {} // 2)
};
Concrete questions:
- Is 1) and 2) the same? Is 2) automatically virtual because of its base or does it "stop" the virtualness?
- Can the derived destructor be omitted if it has nothing to do?
- What's the best practice for declaring the derived destructor? Declare it virtual, non-virtual or omit it if possible?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
final
来防止它在派生类中被重写,但这并不能阻止它成为虚拟的。virtual
关键字或override
。人们不应该必须沿着继承层次结构一路向上才能弄清楚函数是虚拟的。此外,如果您的类是可复制或可移动的,而无需声明您自己的副本或移动构造函数,则声明任何类型的析构函数(即使您将其定义为默认
)将强制您声明副本和如果您需要,请移动构造函数和赋值运算符,因为编译器将不再为您添加它们。作为第 3 项的一个小要点。注释中指出,如果未声明析构函数,编译器会生成默认析构函数(仍然是虚拟的)。默认函数是内联函数。
内联函数可能会将程序的更多部分暴露给程序其他部分的更改,并使共享库的二进制兼容性变得棘手。此外,面对某些类型的更改,增加的耦合可能会导致大量的重新编译。例如,如果您决定确实想要虚拟析构函数的实现,那么调用它的每一段代码都需要重新编译。然而,如果您在类主体中声明了它,然后在
.cpp
文件中将其定义为空,则无需重新编译即可更改它。我个人的选择仍然是尽可能忽略它。在我看来,它会使代码变得混乱,并且编译器有时可以使用默认实现而不是空实现来完成稍微更有效的事情。但你可能会受到一些限制,这使得这是一个糟糕的选择。
final
to prevent it from being overridden in derived classes, but that doesn't prevent it from being virtual.virtual
keyword oroverride
for virtual functions in derived classes for reasons of clarity. People shouldn't have to go all the way up the inheritance hierarchy to figure out that a function is virtual. Additionally, if your class is copyable or movable without having to declare your own copy or move constructors, declaring a destructor of any kind (even if you define it asdefault
) will force you to declare the copy and move constructors and assignment operators if you want them as the compiler will no longer put them in for you.As a small point for item 3. It has been pointed out in comments that if a destructor is undeclared the compiler generates a default one (that is still virtual). And that default one is an inline function.
Inline functions potentially expose more of your program to changes in other parts of your program and make binary compatibility for shared libraries tricky. Also, the increased coupling can result in a lot of recompilation in the face of certain kinds of changes. For example, if you decide you really do want an implementation for your virtual destructor then every piece of code that called it will need to be recompiled. Whereas if you had declared it in the class body and then defined it empty in a
.cpp
file you would be fine changing it without recompiling.My personal choice would still be to omit it when possible. In my opinion it clutters up the code, and the compiler can sometimes do slightly more efficient things with a default implementation over an empty one. But there are constraints you may be under that make that a poor choice.
虚拟成员函数将隐式地使该函数的任何重载成为虚拟的。
因此 1) 中的 virtual 是“可选的”,基类析构函数是 virtual 使得所有子析构函数也都是 virtual 的。
A virtual member function will make implicitely any overloading of this function virtual.
So the virtual in 1) is "optional", the base class destructor being virtual makes all child destructors virtual too.
1/ 是的
2/ 是的,它将由编译器生成
3/ 选择是否将其声明为虚拟成员应遵循您的重写虚拟成员约定 - 恕我直言,两种方式都有很好的论据,只需选择一种并遵循即可。
如果可能的话,我会省略它,但是有一件事可能会促使您声明它:如果您使用编译器生成的编译器,它是隐式内联的。有时您想避免内联成员(例如动态库)。
1/ Yes
2/ Yes, it will be generated by the compiler
3/ The choice between declaring it virtual or not should follow your convention for overriden virtual members -- IMHO, there are good arguments both way, just choose one and follow it.
I'd omit it if possible, but there is one thing which may incite you to declare it: if you use the compiler generated one, it is implicitly inline. There are time when you want to avoid inline members (dynamic libraries for instance).
虚函数被隐式覆盖。当子类的方法与基类的虚函数的方法签名匹配时,它就会被覆盖。
这很容易混淆,并且可能在重构过程中被破坏,因此从 C++11 开始就有了 override 和 final 关键字来明确标记这种行为。有相应的警告禁止静默行为,例如 GCC 中的
-Wsuggest-override
。SO 上有一个关于
override
和final
关键字的相关问题:“override”关键字只是检查重写的虚拟方法吗?。以及 cpp 参考中的文档 https://en.cppreference.com/w/cpp /language/override
是否在析构函数中使用
override
关键字仍然存在争议。例如,请参阅此相关SO问题中的讨论:虚拟析构函数的默认覆盖问题是,虚拟析构函数的语义与普通函数不同。析构函数是链接的,因此所有基类析构函数都在子类之一之后调用。但是,在常规方法的情况下,默认情况下不会调用被重写方法的基本实现。需要时可以手动调用它们。
Virtual functions are overridden implicitly. When the method of a child class matches the method signature of the virtual function from a base class, it is overridden.
This is easy to confuse and possibly break during refactoring, so there are
override
andfinal
keywords since C++11 to mark this behavior explicitly. There is a corresponding warnings that forbid the silent behavior, for example-Wsuggest-override
in GCC.There is a related question for
override
andfinal
keywords on SO: Is the 'override' keyword just a check for a overridden virtual method?.And the documentation in the cpp reference https://en.cppreference.com/w/cpp/language/override
Whether to use
override
keyword with the destructors is still a bit of debate. For example see discussion in this related SO question: default override of virtual destructorThe issue is, that the semantics of the virtual destructor is different to normal functions. Destructors are chained, so all base classes destructors are called after child one. However, in case of a regular method base implementations of the overridden method are not called by default. They can be called manually when needed.