// library.hpp
struct Base {
virtual void f() = 0;
protected:
~Base() = default;
};
void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.
//-------------------
// application.cpp
struct Derived : Base {
void f() override { ... }
};
int main() {
Derived derived;
CallsF(derived);
// No need for virtual destructor here as well.
}
#include<iostream>
using namespace std;
class B{
public:
B(){
cout<<"B()\n";
}
virtual ~B(){
cout<<"~B()\n";
}
};
class D: public B{
public:
D(){
cout<<"D()\n";
}
~D(){
cout<<"~D()\n";
}
};
int main(){
B *b = new D();
delete b;
return 0;
}
OUTPUT:
B()
D()
~D()
~B()
==============
If you don't give ~B() as virtual. then output would be
B()
D()
~B()
where destruction of ~D() is not done which leads to leak
15 回答
虚拟构造函数是不可能的,但虚拟析构函数是可能的 . 让我们实验......
上面的代码输出如下:
派生对象的构造遵循构造规则,但是当我们删除“b”指针(基指针)时,我们发现只有基本析构函数被调用 . 但这不能发生 . 要做适当的事情,我们必须使基础析构函数成为虚拟的 . 现在让我们看看下面发生了什么:
输出更改如下:
因此,基指针的破坏(在派生对象上进行分配!)遵循破坏规则,即首先导出然后是基数 . 另一方面,对于构造函数,没有像虚构造函数那样的东西 .
在多态基类中声明析构函数是虚拟的 . 这是Scott Meyers'Effective C++中的第7项 . Meyers接着总结说,如果一个类有任何虚函数,它应该有一个虚析构函数,那些不是基类或不能用于多态的类不应该声明虚析构函数 .
另请注意,在没有虚析构函数时删除基类指针将导致 undefined behavior . 我刚刚学到的东西:
How should overriding delete in C++ behave?
我已经使用C多年了,我仍然设法挂起自己 .
当您的类具有多态性时,使析构函数成为虚拟的 .
当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数很有用:
在这里,您'll notice that I didn' t声明Base的析构函数为
virtual
. 现在,我们来看看以下代码段:由于Base的析构函数不是
virtual
而且b
是指向Derived
对象的Base*
,delete b
具有undefined behaviour:在大多数实现中,对析构函数的调用将像任何非虚拟代码一样被解析,这意味着将调用基类的析构函数,但不会调用派生类的析构函数,从而导致资源泄漏 .
总而言之,当它们意味着被多态地操纵时,总是使基类的析构函数
virtual
.如果要防止通过基类指针删除实例,可以使基类析构函数受保护且非虚拟;通过这样做,编译器将不允许您在基类指针上调用
delete
.您可以在this article from Herb Sutter中了解有关虚拟性和虚拟基类析构函数的更多信息 .
通过指向基类的指针调用析构函数
虚拟析构函数调用与任何其他虚函数调用没有什么不同 .
对于
base->f()
,调用将被调度到Derived::f()
,对于base->~Base()
它是相同的 - 它的重写函数 - 将调用Derived::~Derived()
.当间接调用析构函数时也会发生同样的情况,例如:
delete base;
.delete
语句将调用base->~Base()
,它将被调度到Derived::~Derived()
.具有非虚析构函数的抽象类
如果您不打算通过指向其基类的指针删除对象 - 那么就不需要有虚拟析构函数 . 只需将其设为
protected
,以便不会被意外调用:我喜欢考虑接口的接口和实现 . 在C语言界面是纯虚拟类 . 析构函数是界面的一部分,有望实现 . 因此析构函数应该是纯虚拟的 . 构造函数怎么样?构造函数实际上不是接口的一部分,因为对象始终是显式实例化的 .
简单来说,当您删除指向派生类对象的基类指针时,Virtual析构函数将以正确的顺序销毁资源 .
当您希望不同的析构函数在对象时遵循正确的顺序时,析构函数的虚拟关键字是必需的正在通过基类指针删除 . 例如:
如果派生类析构函数是虚拟的,那么对象将按顺序被驱逐(首先是派生对象然后是基础) . 如果派生类析构函数不是虚拟的,那么只会删除基类对象(因为指针是基类“Base * myObj”) . 因此派生对象会有内存泄漏 .
什么是虚拟析构函数或如何使用虚拟析构函数
类析构函数是一个与〜前面的类同名的函数,它将重新分配由类分配的内存 . 为什么我们需要一个虚拟析构函数
请参阅以下示例以及一些虚函数
该示例还告诉您如何将字母转换为更高或更低
从上面的示例中,您可以看到未调用MakeUpper和MakeLower类的析构函数 .
使用虚拟析构函数查看下一个示例
虚析构函数将显式调用类的派生最多运行时析构函数,以便它能够以适当的方式清除对象 .
或者访问链接
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
虚拟基类析构函数是“最佳实践” - 您应该始终使用它们来避免(难以检测)内存泄漏 . 使用它们,您可以确保类的继承链中的所有析构函数都被调用(按正确顺序) . 使用虚拟析构函数从基类继承会使继承类的析构函数自动变为虚拟(因此您不必在继承类析构函数声明中重新键入'virtual') .
我认为讨论“未定义”行为或者至少是在没有虚拟析构函数的情况下通过基类(/ struct)删除时可能发生的“崩溃”未定义行为是有益的,或者更确切地说没有vtable . 下面的代码列出了一些简单的结构(对于类来说也是如此) .
我不是在暗示你是否需要虚拟析构函数,尽管我认为一般来说这是一个很好的做法 . 我只是指出如果您的基类(/ struct)没有vtable并且您的派生类(/ struct)没有并且您通过基类(/ struct)删除对象,最终可能会崩溃的原因指针 . 在这种情况下,传递给堆的自由例程的地址无效,因此是崩溃的原因 .
如果您运行上面的代码,您会在问题发生时清楚地看到 . 当基类(/ struct)的this指针与派生类(/ struct)的this指针不同时,你将遇到这个问题 . 在上面的示例中,struct a和b没有vtable . 结构c和d确实有vtables . 因此,将修复指向c或d对象实例的a或b指针以考虑vtable . 如果你将这个或b指针传递给删除它会因为地址对堆的自由例程无效而崩溃 .
如果计划从基类指针中删除具有vtable的派生实例,则需要确保基类具有vtable . 一种方法是添加一个虚拟析构函数,无论如何都可以正确地清理资源 .
我认为这个问题的核心是关于虚方法和多态,而不是具体的析构函数 . 这是一个更清晰的例子:
将打印出来:
如果没有
virtual
,它将打印出来:现在您应该了解何时使用虚拟析构函数 .
当你需要从基类调用派生类析构函数时 . 你需要在基类中声明虚基类析构函数 .
任何公开继承的类,多态的或不具有的,都应该有一个虚拟的析构函数 . 换句话说,如果它可以由基类指针指向,则其基类应该具有虚拟析构函数 .
如果是virtual,则派生类析构函数被调用,然后是基类构造函数 . 如果不是虚拟的,则只调用基类析构函数 .