// 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
1
什么是虚拟析构函数或如何使用虚拟析构函数
类析构函数是具有相同类名的函数在〜之前将重新分配由类分配的内存 . 为什么我们需要一个虚拟析构函数
请参阅以下带有一些虚函数的示例
该示例还告诉您如何将字母转换为上限或下限
#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
~convertch(){};
};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
Letter[i] = Letter[i] + 32;
return Letter;
}
private:
char *Letter;
bool tolower;
};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
Letter[i] = Letter[i] - 32;
return Letter;
}
virtual ~MakeUpper()
{
cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};
int _tmain(int argc, _TCHAR* argv[])
{
convertch *makeupper = new MakeUpper("hai");
cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";
delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" ";
delete makelower;
return 0;
}
从上面的示例中,您可以看到未调用MakeUpper和MakeLower类的析构函数 .
使用虚拟析构函数查看下一个示例
#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor
};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;
}
return Letter;
}
private:
char *Letter;
bool tolower;
};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};
int _tmain(int argc, _TCHAR* argv[])
{
convertch *makeupper = new MakeUpper("hai");
cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";
delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";
delete makelower;
return 0;
}
15 回答
通过指向基类的指针调用析构函数
虚拟析构函数调用与任何其他虚函数调用没有区别 .
对于
base->f()
,调用将被调度到Derived::f()
,对于base->~Base()
它是相同的 - 它的重写函数 - 将调用Derived::~Derived()
.当间接调用析构函数时也会发生相同的情况,例如
delete base;
.delete
语句将调用base->~Base()
,它将被调度到Derived::~Derived()
.具有非虚析构函数的抽象类
如果您不打算通过指向其基类的指针删除对象 - 那么就不需要有虚拟析构函数 . 只需将其设为
protected
,以免意外调用:当您希望不同的析构函数在通过基类指针删除对象时遵循正确的顺序时,析构函数的虚拟关键字是必需的 . 例如:
如果派生类析构函数是虚拟的,那么对象将按顺序被驱逐(首先是派生对象然后是基础) . 如果派生类析构函数不是虚拟的,则只删除基类对象(因为指针是基类“Base * myObj”) . 因此派生对象会有内存泄漏 .
虚拟构造函数是不可能的,但虚拟析构函数是可能的 . 让我们实验吧......
上面的代码输出如下:
派生对象的构造遵循构造规则,但是当我们删除“b”指针(基指针)时,我们发现只有基本析构函数被调用 . 但是这不能发生 . 要做适当的事情,我们必须使基础析构函数成为虚拟的 . 现在让我们看看下面发生了什么:
输出更改如下:
因此,基指针的破坏(在派生对象上进行分配!)遵循破坏规则,即首先导出然后是基数 . 另一方面,对于构造函数,没有像虚构造函数那样的东西 .
当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数很有用:
在这里,你'll notice that I didn' t声明Base的析构函数为
virtual
. 现在,我们来看看以下代码段:由于Base的析构函数不是
virtual
而且b
是Base*
指向Derived
对象,delete b
有undefined behaviour:在大多数实现中,对析构函数的调用将像任何非虚拟代码一样被解析,这意味着将调用基类的析构函数而不是派生类的析构函数,从而导致资源泄漏 .
总而言之,当它们意味着被多态地操纵时,总是使基类的析构函数
virtual
.如果要防止通过基类指针删除实例,可以使基类析构函数受保护且非虚拟化;通过这样做,编译器将不允许您在基类指针上调用
delete
.您可以在this article from Herb Sutter中了解有关虚拟性和虚拟基类析构函数的更多信息 .
我喜欢考虑接口的接口和实现 . 在C语言界面是纯虚拟类 . 析构函数是界面的一部分,有望实现 . 因此析构函数应该是纯虚拟的 . 构造函数怎么样?构造函数实际上不是接口的一部分,因为对象始终是显式实例化的 .
当你需要从基类调用派生类析构函数时 . 你需要在基类中声明虚基类析构函数 .
我认为这个问题的核心是关于虚方法和多态,而不是具体的析构函数 . 这是一个更清晰的例子:
将打印出来:
如果没有
virtual
,它将打印出来:现在您应该了解何时使用虚拟析构函数 .
还要注意,在没有虚析构函数时删除基类指针将导致 undefined behavior . 我最近刚学到的东西:
How should overriding delete in C++ behave?
我已经使用C多年了,我仍然设法挂起自己 .
简单来说,当您删除指向派生类对象的基类指针时,Virtual析构函数将以正确的顺序销毁资源 .
什么是虚拟析构函数或如何使用虚拟析构函数
类析构函数是具有相同类名的函数在〜之前将重新分配由类分配的内存 . 为什么我们需要一个虚拟析构函数
请参阅以下带有一些虚函数的示例
该示例还告诉您如何将字母转换为上限或下限
从上面的示例中,您可以看到未调用MakeUpper和MakeLower类的析构函数 .
使用虚拟析构函数查看下一个示例
虚析构函数将显式调用类的最派生运行时析构函数,以便它能够以适当的方式清除对象 .
或者访问链接
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
任何公开继承的类,无论是否多态,都应该有一个虚拟析构函数 . 换句话说,如果它可以由基类指针指向,它的基类应该有一个虚析构函数 .
如果是virtual,则派生类析构函数被调用,然后是基类构造函数 . 如果不是虚拟的,则只调用基类析构函数 .
在多态基类中声明析构函数是虚拟的 . 这是Scott Meyers的第5项Effective C++ . Meyers接着总结说,如果一个类有任何虚函数,它应该有一个虚拟析构函数,并且那些不是基类或不能用于多态的类不应该声明虚拟析构函数 .
当您的类具有多态性时,使析构函数成为虚拟的 .
我认为讨论“未定义”行为或至少在没有虚拟析构函数的情况下通过基类(/ 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') .