在我的设计过程中,有时我会添加/或删除虚拟方法。我知道的经验法则是,一旦我有了虚拟方法,我就会有一个虚拟析构函数。
我的问题:如果我在创建类时立即添加虚拟析构函数(即使还没有虚拟方法),会有什么害处吗?基本上这个想法是不要忘记它。特别是对于 n 个派生类,我以后不需要在 n 个地方更改它。
在我的设计过程中,有时我会添加/或删除虚拟方法。我知道的经验法则是,一旦我有了虚拟方法,我就会有一个虚拟析构函数。
我的问题:如果我在创建类时立即添加虚拟析构函数(即使还没有虚拟方法),会有什么害处吗?基本上这个想法是不要忘记它。特别是对于 n 个派生类,我以后不需要在 n 个地方更改它。
虚函数表的大小有很小的开销。可能不值得担心。虚拟析构函数还将使您的类成为非聚合类、非平凡类、非标准布局类,因此也是非 POD 类。这可能是不可取的,具体取决于手头的问题。
但是,我建议专门将您的类设计为多态或非多态。如果要以多态方式使用它们,请给它们一个虚拟析构函数。如果没有,不要。如果您需要更改它,请在需要时进行。
唯一的危害是你的类和所有派生类都会有一个 v-table,它的大小会略有增加。即使您决定稍后将基类析构函数设为虚拟,您也不需要对派生类进行任何更改。对于包括析构函数在内的任何方法,您只需在基类中使用一次虚拟关键字。派生类中的相同方法自动变为虚拟。
作为替代方案,您可以保护您的析构函数。这将防止使用基类指针意外调用删除。
class A
{
protected:
~A(){}
};
class B : public A
{};
int main(int argc, char *argv[])
{
A * p = new B;
delete p;
}
在我的编译器上,它给出了以下错误
错误 C2248:“A::~A”:无法访问在“A”类中声明的受保护成员 a.cpp(9):编译器在此处生成了“A::~A”a.cpp(6):参见“声明”一个'
不,拥有一个虚拟析构函数是非常有意义的,即使你没有任何其他虚拟方法。
但是,如果内存使用很重要,那么每个字节都很重要,如果您没有任何虚拟方法,您可以获得 4 或 8 个字节。在我的应用程序中,我有一些类,其中有数百万个实例。在这种情况下,在你的类中去掉一个 v 指针真的很有意义。
如果将析构函数从非虚拟更改为虚拟(反之亦然),我不完全理解为什么需要更改派生类。一旦一个方法是虚拟的,同样的方法在所有派生类中都是虚拟的,即使你没有指定虚拟。然而,由于风格原因,可能建议在派生类中添加 virtual,即使它不是必需的。
不要盲目遵守规则。也就是遵守规则,但不要盲目行事。
真正需要虚拟析构函数的唯一情况是通过其基对象指针删除对象时。经验法则概括并简化了这个条件:如果一个对象有可能通过它的基对象指针被删除,那么它将被多态地使用;多态对象很可能具有虚函数,具有虚函数的对象很可能被多态使用;因此,具有虚函数的对象很可能需要虚析构函数。
这一切都很好,花花公子,这条规则大多是有效的,但有一个更重要、更基本的事实很少被提及,部分原因是这些规则确实有效。事实是,有类似值的对象,也有其他类型的对象,它们没有一个好名字,但我将它们称为实体对象。类实体对象具有与其值分离的标识,它们使用引用语义,不应在没有充分理由的情况下复制它们(例如创建单独的标识),它们很可能被多态访问等。类值对象没有标识除了它们的价值之外,它们可以自由复制,它们不应该被多态地使用等等。它们是如此的不同,为它们的类设置不同的关键字是值得的!当你设计你的类时,你必须决定它属于哪个类别。然后你的问题就会自行解决。实体获得虚拟析构函数,值没有。
拥有虚拟析构函数的唯一原因是可以通过指向基类型的指针删除派生类型的对象。如果这是类的设计所要求的,那么它必须有一个虚拟析构函数,即使它没有任何其他虚拟函数。如果设计不包括通过指向基址的指针进行删除,那么即使它具有虚函数,它也不需要虚析构函数。束手无策的人会告诉你无论如何都要让析构函数成为虚拟的,因为它不会伤害任何东西,而且,你永远不知道。这不是技术原因。这是防止不阅读和遵循文档的用户的策略选择。