关于c ++:没有虚拟方法的虚拟析构函数有什么危害吗?

Is there any harm having a virtual destructor without a virtual method?

本问题已经有最佳答案,请猛点这里访问。

在设计过程中,有时我会遇到添加/删除虚拟方法的情况。我知道的经验法则是,一旦我有了虚拟方法,我将拥有一个虚拟析构函数。

我的问题是:如果我在创建类时直接添加一个虚拟析构函数,会有什么害处吗(即使还没有虚拟方法)?基本上,我们的想法是以后不要忘记它。尤其是对于n个派生类,以后我不需要在n个地方更改它。


虚拟函数表的大小有很小的开销。可能不值得担心。虚拟析构函数还将使类成为非聚合类、非平凡类、非标准布局类,因此也将成为非pod类。这可能是不可取的,具体取决于手头的问题。

但是,我建议将类专门设计为多态的或非多态的。如果它们将被多态使用,给它们一个虚拟的析构函数。如果没有,不要。如果你需要改变它,当你需要的时候就去做。


不要盲目地遵守规则。也就是说,遵守规则,但不要盲目地去做。

当一个虚拟析构函数真正必要时,唯一的情况是当一个对象通过它的基对象指针被删除。经验法则概括并简化了这种情况:如果一个对象可能通过其基对象指针被删除,那么它将被多态地使用;一个多态对象可能有虚拟函数,而一个具有虚拟函数的对象可能被多态地使用;因此,一个具有虚拟函数的对象S可能需要一个虚拟析构函数。

这一切都很好,而且很花哨,这条规则大部分是有效的,但是有一个更重要和更基本的事实很少被提及,部分原因是这样的规则确实有效。事实上,有价值的对象,也有其他类型的对象,它们没有好的名称,但是我会称它们为实体类的对象。像实体一样的对象具有独立于其值的标识,它们使用引用语义,没有好的理由不应该复制它们(例如创建单独的标识),它们很可能被多态访问等等。像对象一样的值除了其值之外没有标识,它们可以自由复制,不应该被多态使用。凯莉,等等。他们是如此的不同,有不同的关键字为他们的类是值得的!当你设计你的课程时,你必须决定它属于哪个类别。然后你的问题就解决了。实体获取虚拟析构函数,而值不获取。


不,使用虚拟析构函数是完全有意义的,即使您没有任何其他虚拟方法。

但是,如果内存使用很重要,那么每个字节都会计数,如果没有任何虚拟方法,则可以获得4或8个字节。在我的应用程序中,我有一些类,其中有数百万个实例。在这种情况下,去掉类中的v指针是很有意义的。

如果您将析构函数从非虚拟更改为虚拟(或相反),那么我不完全理解为什么需要更改派生类。一旦一个方法是虚拟的,那么同一个方法在所有派生类中都将是虚拟的,即使您没有指定虚拟的。然而,由于样式原因,建议在派生类中添加虚拟的,即使不需要。


唯一的危害是类和所有派生类都有一个V表,它的大小略有增加。即使您稍后决定将基类析构函数设为虚拟的,也不需要在派生类中进行任何更改。对于任何方法,包括析构函数,您只需要在基类中使用一次虚拟关键字。派生类中的相同方法自动变为虚拟方法。

作为替代方法,您可以使析构函数受到保护。这将防止使用基类指针意外调用delete。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A
{
    protected:
    ~A(){}
};

class B : public A
{};

int main(int argc, char *argv[])
{
    A * p = new B;
    delete p;
}

在我的编译器上,它给出了以下错误

error C2248: 'A::~A' : cannot access protected member declared in class 'A'
a.cpp(9) : compiler has generated 'A::~A' here
a.cpp(6) : see declaration of 'A'


拥有虚拟析构函数的唯一原因是可以通过指向基类型的指针删除派生类型的对象。如果类的设计需要这样做,那么它必须有一个虚拟析构函数,即使它没有任何其他虚拟函数。如果设计不包括通过指向基的指针删除,那么它不需要虚拟析构函数,即使它具有虚拟函数也是如此。安全带和吊带的人会告诉你,无论如何,让破坏者虚拟,因为它不会伤害任何东西,而且,嗯,你只是永远不知道。这不是一个技术原因;这是一个保护不阅读和遵循文档的用户的策略选择。