博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
什么时候析构函数需要定义为虚函数
阅读量:4114 次
发布时间:2019-05-25

本文共 2550 字,大约阅读时间需要 8 分钟。

C++:构造函数和析构函数能否为虚函数?

简单回答是:构造函数不能为虚函数,而析构函数可以且常常是虚函数。

(1) 构造函数不能为虚函数

让我们来看看大牛C++之父 Bjarne Stroustrup 在《The C++ Programming Language》里是怎么说的:

To construct an object, a constructor needs the exact type of the object it is to create. Consequently, a constructor cannot be virtual. Furthermore, a constructor is not quite an ordinary function, In particular, it interacts with memory management in ways ordinary member functions don't. Consequently, you cannot have a ponter to a constructor.

--- From 《The C++ Progamming Language》15.6.2

然而大牛就是大牛,这段话对一般人来说太难理解了。那下面就试着解释一下为什么:

这就要涉及到C++对象的构造问题了,C++对象在三个地方构建:(1)函数堆栈;(2)自由存储区,或称之为堆;(3)静态存储区。无论在那里构建,其过程都是两步:首先,分配一块内存;其次,调用构造函数。好,问题来了,如果构造函数是虚函数,那么就需要通过vtable 来调用,但此时面对一块 raw memeory,到哪里去找 vtable 呢?毕竟,vtable 是在构造函数中才初始化的啊,而不是在其之前。因此构造函数不能为虚函数。

 

(2)析构函数可以是虚函数,且常常如此

这个就好理解了,因为此时 vtable 已经初始化了;况且我们通常通过基类的指针来销毁对象,如果析构函数不为虚的话,就不能正确识别对象类型,从而不能正确销毁对象。

 

 

困惑我们的是我们却经常看到“虚构造函数”这样的说法,这就要归咎于不负责任或者说误人子弟的媒体了(包括书、技术文章等等)。因为他们说的是类似下面这样的做法:

class Expr {

public:

     Expr();

     Expr(const Expr&);

     virtual Expr* new_expr() { return new Expr(); }

     virtual Expr* clone() { return new Expr(*this); }

};

___________________________________________________________________________________________________

参考资料:

1.每个析构函数(不加 virtual) 只负责清除自己的成员。

2.可能有基类指针,指向的确是派生类成员的情况。(这是很正常的),
   那么当析构一个指向派生类成员的基类指针时,程序就不知道怎么办了。 
   所以要保证运行适当的析构函数,基类中的析构函数必须为虚析构。

        基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。

 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:    

    有下面的两个类:

class
 ClxBase
{
public
:
    ClxBase() {};
    
virtual
 
~
ClxBase() {};
    
virtual
 
void
 DoSomething() { cout 
<<
 
"
Do something in class ClxBase!
"
 
<<
 endl; };
};
class
 ClxDerived : 
public
 ClxBase
{
public
:
    ClxDerived() {};
    
~
ClxDerived() { cout 
<<
 
"
Output from the destructor of class ClxDerived!
"
 
<<
 endl; }; 
    
void
 DoSomething() { cout 
<<
 
"
Do something in class ClxDerived!
"
 
<<
 endl; };
};

    代码

ClxBase 
*
pTest 
=
 
new
 ClxDerived;
pTest
->
DoSomething();
delete pTest;

    的输出结果是:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!

    这个很简单,非常好理解。

    但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:

Do something in class ClxDerived!

    也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。

    所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
    当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

转载地址:http://kogsi.baihongyu.com/

你可能感兴趣的文章
idea的安装以及简单使用
查看>>
Windows mysql 安装
查看>>
python循环语句与C语言的区别
查看>>
Vue项目中使用img图片和background背景图的使用方法
查看>>
vue 项目中图片选择路径位置static 或 assets区别
查看>>
vue项目打包后无法运行报错空白页面
查看>>
Vue 解决部署到服务器后或者build之后Element UI图标不显示问题(404错误)
查看>>
element-ui全局自定义主题
查看>>
facebook库runtime.js
查看>>
vue2.* 中 使用socket.io
查看>>
openlayers安装引用
查看>>
js报错显示subString/subStr is not a function
查看>>
高德地图js API实现鼠标悬浮于点标记时弹出信息窗体显示详情,点击点标记放大地图操作
查看>>
初始化VUE项目报错
查看>>
vue项目使用安装sass
查看>>
HTTP和HttpServletRequest 要点
查看>>
在osg场景中使用GLSL语言——一个例子
查看>>
关于无线PCB中 中50欧姆的特性阻抗的注意事项
查看>>
Spring的单例模式源码小窥
查看>>
后台服务的变慢排查思路(轻量级应用服务器中测试)
查看>>