Possible Duplicate:
When to use virtual destructors?
如果类(具有虚函数)及其继承类的所有数据成员都是非指针类型(意味着它不能保存任何动态内存),是否需要将析构函数声明为虚函数?
例子
1 2 3 4 5 6 7 8 9 10 11 12 13
| class base {
int x;
public:
virtual void fn(){}
};
class der: public base {
int y;
public:
void fn(){}
}; |
这里我们需要一个虚拟析构函数吗?
不,这并不总是必要的。这只是一个经验法则,因此并不总是适用的。
真正的规则是:
A destructor must be declared virtual when objects of derived classes are to be deleted through base class pointers.
否则,通过基类指针删除派生类对象会调用未定义的行为。(最可能的结果是只调用基类的析构函数。)
当然,这条规则对于新手来说是一个很好的选择,因此更简单的经验法则几乎总是正确的。很可能您是通过多态类层次结构中的基类指针来管理动态创建的派生类对象,而对于非多态类层次结构,这样做的可能性非常小。
不,这是不需要的,而且一直这样做可能会损害性能。
除非实际存储派生类对象的基类指针为delete,否则不会遇到ub(未定义的行为)。因此,是否需要虚拟析构函数取决于代码实际创建和释放对象的方式,而不仅仅取决于类。
btw与基类相比,派生类是否需要任何额外的破坏并不重要——在delete应用于存储派生类对象地址的基类指针为ub的情况下,缺少虚拟破坏函数。
- 关于性能:只要您内联析构函数体,它就不应该真正重要。
- @matthieu m:通常使用vtable调用虚拟析构函数-此类调用不内联。
- 伤害表现?怎样?vtable的成本已经支付(问题中的假设是至少有一个其他虚拟功能)。现在,如果对象是通过指向基的指针销毁的,那么这是一个要求,如果对象具有自动存储,那么调用将被静态调度。只有当它们通过指向完整对象的指针是deleted时,动态调度的成本才会受到影响,即使这样,它也可以忽略不计。这种关于性能的误导性评论是新程序员为了避免不存在的成本而做错事的原因。
- @sharptooth:实际上不是。如果满足两个条件,则使用vtable调用virtual析构函数:调用不精确应使用哪种静态类型,并且无法推断该静态类型。如果静态类型已知,则可以内联调用。这对于析构函数能够内联它们很重要,因为子类静态地调用基类析构函数。
- 注意:我假设ub是指未定义的行为,尽管我以前从未见过这样的行为。
- @彼得伍德UB在这里经常被用作未定义行为的简写,在许多情况下,在注释中,字符数可能是一个限制因素。
- @David Rodr&237;Guez-Dribeas:我甚至有一个为虚拟析构函数stackoverflow.com/q/8052146/57428生成额外代码的例子,在写这个答案之前,我已经完成了我的家庭作业,我说可以伤害,不会伤害。
- @Matthieu M:它可以是内联的,但并不意味着它会是内联的。Visual C++ 10不能做任何这样的演绎。
- @ MatthieuM。还要注意,调用是动态的情况可以分为两类:当指针的静态类型不是实际对象的类型时,在这种情况下,讨论的不是性能,而是正确性(非虚拟析构函数是ub),以及类型相同的情况,这是唯一存在的情况是一个可忽略的成本(每个对象的额外间接性不会影响运行时)
- @sharptoth链接的问题与析构函数是否是虚拟的无关,而与析构函数是否由delete调用相反,即需要处理定位内存。许多编译器将在C++中为每个构造函数生成3个构造函数,而两个析构函数始终不考虑析构函数是否为虚函数。用那个例子,使析构函数非虚拟化,尝试使用自动存储的对象…唯一额外的费用将是通过vtable的指示,并且仅在少数情况下不需要正确性。
- @Davidrodr&237;Guez Dribeas:实际上,与常规通话相比,虚拟通话本身并不是什么问题。真正影响性能的是,如果不推导静态调用,那么它就不能内联。
- @夏普图思:如果你不得不使用Visual Studio,我很遗憾。我很久以前就不再关心微软对C++的不良支持:我只是避开了它。
虚析构函数确保在有指向基类的指针时调用继承的类析构函数。
在这种特殊情况下,您不需要它,但是用户可以从der继承另一个类(让它成为foo),该类使用动态内存分配(例如)。在这种情况下,除非析构函数具有类型为foo的指针,否则不会调用该析构函数。
所以不,这不是"必要的",但是如果你已经有了一个虚拟功能(因此你已经有了一个vtable),那么也没有什么害处。如果您假定这些类将由用户继承,并且使用指向基类的指针释放,那么这是必需的。
对。
任何时候使用虚函数创建类时,都需要将析构函数也声明为虚函数。
考虑这种情况-
1 2
| base *b = new der();
delete b; |
因为您是在基指针上操作的,所以它不知道它实际上是子类的对象,因此永远不会调用der的析构函数。这可能会导致记忆泄漏和其他问题。
- 错了。即使基类没有任何虚拟函数,它也是ub,通过基指针删除派生类的实例。