接口的虚析构函数

10

接口类是否需要虚析构函数,或者自动生成的析构函数就足够了?例如,以下两个代码片段中哪一个更好,为什么?请注意,这是整个类。没有其他方法、变量等。在Java中,这被称为“接口”。

class Base
{
public:
    virtual void foo() = 0;
    virtual ~Base() {}
};

或者...

class Base
{
public:
    virtual void foo() = 0;
    ~Base() {} // This line can be omitted, but included for clarity.
};

由于“不是我所寻找的”答案而进行的编辑:

每种方法的后果究竟是什么?请不要给出含糊的答案,比如“它不会被正确地销毁”。请告诉我会发生什么。我有一点汇编迷。

Edit 2:

我很清楚“virtual”标记意味着如果通过指向派生类的指针删除,则析构函数不会被调用,但(我认为)这个问题归根结底是“省略析构函数是安全的吗,因为它真的很简单吗?”

编辑3:

我的第二次编辑是完全错误的和误导性的。请阅读实际聪明人的评论获取更多信息。


3
您对虚拟函数的定义有些误解。虚拟标签并不意味着如果通过指向派生类的指针进行删除,基本析构函数不会被调用 - 这种情况总是发生的。相反,虚拟标签意味着如果您通过基类类型的指针删除对象,则会调用派生类的析构函数。 - Jordan Lewis
1
@Jordan:天啊,我想你是对的。 - Clark Gaebel
4
“我有点像一个汇编迷”。不幸的是,C++ 不涉及生成汇编代码。如果你想看看特定编译器如何处理它,可以编译一些代码并查看输出。关于你的态度,我要给你-1分;不要问问题然后告诉所有回答者“你们搞反了”,然后给他们投反对票。你提出问题是有原因的,是吧? - GManNickG
只是让你知道 - 我弄明白后取消了踩票。 - Clark Gaebel
@wowus:好的,我的也没了。 :) - GManNickG
你的绝对诚实让我大笑了 - 有时候我们都需要向更聪明的人寻求帮助,这就是SO存在的意义。 - reshen
6个回答

13

考虑以下情况:

   Base *Var = new Derived();
   delete Var;

你需要虚析构函数,否则当你删除 Var 时,派生类的析构函数将永远不会被调用。


它没有值得一提的析构函数。 - Clark Gaebel
2
@wowus:Base没有值得一提的析构函数。你不知道Derived的析构函数做了什么。 - Dennis Zickefoose
2
你在这里忽略了关键点。除非你将Base::Base声明为虚函数,否则Derived::Derived不会被调用。 - Peter Kovacs
3
不,是你搞错了。在 Base 中缺少虚析构函数意味着当使用 Base* 类型指针删除时,只有 Base::~Base 会被调用。如果 Base 的析构函数是虚的,那么派生类的析构函数也会被调用。 - Tyler McHenry
1
@bshields,如果指针是Derived*类型,则无论析构函数是否为虚函数,都将调用Derived和Base。 - Mark Ransom
显示剩余2条评论

7
如果你在C++中通过基类指针删除派生类对象,结果是未定义的行为。未定义行为是你真的想要避免的,因此必须给基类一个虚析构函数。引用C++标准第5.3.5节的话来说:
“如果操作数的静态类型与其动态类型不同,则静态类型应该是操作数的动态类型的基类,并且静态类型应该具有虚析构函数,否则行为未定义。”

3

如果你希望使用父类的指针或引用来删除派生类的对象,那么你应该使用虚析构函数。如果没有虚析构函数,派生类将永远无法被正确销毁。

例如:

Derived::~Derived() { // important stuff }
Base *foo = new Derived();
delete foo;

如果Base类中没有虚析构函数,Derived类的析构函数将永远不会被调用,因此重要的事情将永远不会发生。


4
实际上,在这种情况下,C++未定义该行为。 - anon

3

主要回复编辑:

没有人能告诉你会发生什么,因为结果是“未定义的行为”。当你通过指向没有虚析构函数的基类的指针删除派生类时,实现可以以任何数量的方式崩溃。


2
通常来说,析构函数应该是公有的并且是虚函数,或者是受保护的非虚函数。
假设您不希望通过接口指针删除类实例,那么受保护的非虚析构函数是百分之百安全的。
如果在情况(2)中尝试删除接口指针,将会得到编译时错误。

你的第一句话是不是想说“析构函数(destructor)”? - Justin Ethier

0

不是的...虚拟析构函数不会自动生成。您必须在基类中显式声明它们。 但是,对于Base的子类,您不需要声明其析构函数为虚函数。这由编译器完成。 编译器还会确保按照构造顺序相反的顺序调用析构函数(从派生类到基类)。

public class Base 
{
 //...
}

public class Derived
{
   int i = 0;
   //...
}

//...

Base* b = new Derived();

如果你没有虚析构函数

delete b;

会导致内存泄漏(至少4个字节的整数字段),因为它只会销毁 Base 而不是 Derived 。虚拟性确保派生类也被销毁。您不必在 Derived 中声明虚构造函数,如果您在 Base 中声明了虚拟析构函数,则编译器会自动推断。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接