4.4 C++虚析构函数
参考:http://www.weixueyuan.net/view/6373.html
总结:
构造函数是不能声明为虚函数的,析构函数可以被声明为虚函数。
将基类的析构函数声明为虚函数之后,派生类的析构函数也自动成为虚析构函数。
未将基类的析构函数定义为虚函数,如下面的例子的情况可能会出现内存泄漏。原因是不构成多态,函数属于编译期绑定,无论基类指针p指向的是派生类对象或者是基类对象,执行的都将会是基类的函数。
通常来说,如果基类中存在一个指向动态分配内存的成员变量,并且基类的析构函数中定义了释放该动态分配内存的代码,则应该将基类的析构函数声明为虚函数。
只有非静态成员函数才可以成为虚函数,而静态成员函数不能声明为虚函数。
在类中,构造函数用于初始化对象及相关操作,构造函数是不能声明为虚函数的,因为在执行构造函数前对象尚未完成创建,虚函数表尚不存在,此时就无法去查询虚函数表,因此也就无法得知该调用哪一个构造函数了。
析构函数则用于销毁对象时完成相应的资源释放工作,析构函数可以被声明为虚函数。我们先通过一个例子来说明析构函数声明为虚函数的必要性。
例1:
#include<iostream>
using namespace std; class base
{
public:
base();
~base();
private:
int * a;
}; class derived: public base
{
public:
derived();
~derived();
private:
int * b;
}; base::base()
{
cout<<"base constructor!"<<endl;
a = new int[];
} base::~base()
{
cout<<"base destructor!"<<endl;
delete[] a;
} derived::derived()
{
cout<<"derived constructor!"<<endl;
b = new int[];
} derived::~derived()
{
cout<<"derived destructor!"<<endl;
delete[] b;
} int main()
{
base* p;
p = new derived;
delete p;
return ;
}
在本类中定义了两个类,一个基类,一个派生类,派生类和基类中都分别定义了自己的构造函数和析构函数。基类和派生类中各有一个int型指针成员变量,在基类的构造函数中,给指针变量a分配了10个int型空间,在基类的析构函数则用于将是将a所指向的空间释放掉,在派生类的构造函数中,指针成员变量被分配了1000个整型空间,派生类的析构函数则是为了释放掉b指针所指向的存储空间。在主函数中,我们创建一个基类类型的指针,指针指向一个派生类对象,之后释放p指针所指向的对象的存储空间。最后程序运行结果如下:
base constructor!
derived constructor!
base destructor!
观察程序运行结果,程序打印出了“base constructor!”这串字符,则说明基类的构造函数被调用了,之后又打印出了“derived constructor!”这串字符,同样地派生类的构造函数也被调用了。当我们用new操作符创建一个派生类对象时会先调用基类构造函数,然后再调用派生类构造函数,程序输出结果与我们料想的是一致的。至此基类的成员变量a通过构造函数被分配了10个整型存储空间,派生类的成员变量b通过构造函数被分配了1000个整型存储空间。之后程序打印出了“base destructor!”字符串,这说明基类的析构函数被调用了,a指针所指向的10个整型内存空间被释放了。但是之后却并未调用派生类的析构函数,不调用派生类的析构函数则会导致b指针所指向的1000个整型存储空间不会被释放,如此一来造成了内存泄露了。内存泄露问题肯定是我们程序设计人员需要极力避免的。本例中出现的问题就是因为派生类的析构函数未被调用,为了解决这个问题,我们将基类的析构函数声明为虚函数,修改后基类的定义如下:
class base
{
public:
base();
virtual ~base();
private:
int * a;
};
修改基类的定义后,程序运行结果如下:
base constructor!
derived constructor!
derived destructor!
base destructor!
将基类的析构函数声明为虚函数之后,派生类的析构函数也自动成为虚析构函数,在主函数中基类指针p指向的是派生类对象,当delete释放p指针所指向的存储空间时,会执行派生类的析构函数,派生类的析构函数执行完之后会紧接着执行基类的析构函数,以释放从基类继承过来的成员变量所消耗的资源。如此一来就不会存在内存泄漏问题了。
从此例中我们很明显可以看出析构函数声明为虚函数的必要性,但是如果不管三七二十一的将所有的基类的析构函数都声明为虚函数,这也是不合适的。通常来说,如果基类中存在一个指向动态分配内存的成员变量,并且基类的析构函数中定义了释放该动态分配内存的代码,则应该将基类的析构函数声明为虚函数。
只有非静态成员函数才可以成为虚函数,而静态成员函数不能声明为虚函数。
例1:
class test
{
public :
virtual test(){a = new int[];} //error
static void g(); //ok
virtual void f(); //ok
virtual static void h(); //compile error
virtual ~test(){delete[] a;} //ok
private:
int * a;
};
在本例中定义了一个test类,这个类中有一个指针成员变量a,test类中有五个成员函数,在本例中将析构函数和普通成员函数f声明为虚函数是没有问题的,将构造函数和静态成员函数声明为虚函数则会出现编译错误,这两种做法都是有违C++语法规定的。
4.4 C++虚析构函数的更多相关文章
- c++虚析构函数
虚析构函数的作用主要是当通过基类指针删除派生类对象时,调用派生类的析构函数(如果没有将不会调用派生类析构函数) #include <iostream> using namespace st ...
- EC笔记,第二部分:7.为多态基类声明虚析构函数
7.为多态基类声明虚析构函数 1.为多态基类声明虚析构函数 code1: class A{ public: int* a; A():a(new int(5)) {} ~A(){ delete a; } ...
- C++浅析——继承类内存分布和虚析构函数
继承类研究 1. Code 1.1 Cbase, CTEST为基类,CTest2为其继承类,并重新申明了基类中的同名变量 class CBase { public: int Data; CBase() ...
- 虚析构函数(√)、纯虚析构函数(√)、虚构造函数(X)
from:http://blog.csdn.net/fisher_jiang/article/details/2477577 一. 虚析构函数 我们知道,为了能够正确的调用对象的析构函数,一般要求具有 ...
- C++学习24 虚析构函数
在C++中,构造函数用于在创建对象时进行初始化工作,不能声明为虚函数.因为在执行构造函数前对象尚未创建完成,虚函数表尚不存在,也没有指向虚函数表的指针,所以此时无法查询虚函数表,也就不知道要调用哪一个 ...
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数
一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...
- 从零开始学C++之虚函数与多态(二):纯虚函数、抽象类、虚析构函数
一.纯虚函数 虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 在基类中不能给出有意义 ...
- C/C++中的虚析构函数和私有析构函数的使用
代码: #include <iostream> using namespace std; class A{ public: A(){ cout<<"construct ...
- C++中基类的析构函数为什么要用virtual虚析构函数
知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅: C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...
- c++ 虚析构函数[避免内存泄漏]
c++ 虚析构函数: 虚析构函数(1)虚析构函数即:定义声明析构函数前加virtual 修饰, 如果将基类的析构函数声明为虚析构函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚析构函数. ...
随机推荐
- (原创)PBS | SGE 智能任务投递系统monitor | python实现
之前看到过高手写的一个monitor,用python面向对象实现的,依赖几个核心的python包,drmaa,zodb,理论上来说解决了所有的任务投递问题. 但是在复杂的集群环境下还是会经常出问题,这 ...
- Bayesian generalized linear model (GLM) | 贝叶斯广义线性回归实例
一些问题: 1. 什么时候我的问题可以用GLM,什么时候我的问题不能用GLM? 2. GLM到底能给我们带来什么好处? 3. 如何评价GLM模型的好坏? 广义线性回归啊,虐了我快几个月了,还是没有彻底 ...
- 使用Tomcat数据源的方式访问数据库(MySql) --Struts2框架应用与开发
1.为方便测试首先创建数据库和表,然后插入测试数据 2.打开Tomcat服务器安装目录的conf/下的context.xml,配置context.xml文件. 在<Context>标签 ...
- LeetCode--682--棒球比赛(java)
你现在是棒球比赛记录员. 给定一个字符串列表,每个字符串可以是以下四种类型之一:1.整数(一轮的得分):直接表示您在本轮中获得的积分数.2. "+"(一轮的得分):表示本轮获得的得 ...
- 20170706xlVBA批量提取word表格中的自我评分
单位里普遍存在各种低效率的办公行为,比如每年的自我评分.评分细目表为word文档,每行一个项目,每个项目要填写得分事项和分值,组长审核之后转成Excel向上递交.主要涉及到问题就是word文档中一列得 ...
- 关于新手用java写题目,遇到的字符和字符串问题
我看到一遍很好的博客: https://blog.csdn.net/qq_37267015/article/details/78738512 1.首先了,java之中,没有像C语言那样的getchar ...
- hdu-5985 概率DP
http://acm.hdu.edu.cn/showproblem.php?pid=5985 作为队里负责动态规划的同学,做不出来好无奈啊.思考了一个下午,最好还是参考了别人的思想才写出来,数学啊!! ...
- Android开源框架源码分析:Okhttp
一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...
- centos 7 源代码安装mysql5.6
###### mysql ######### 引言:这里选用mysql5.6版本,5.7版本编译时间需要几个小时. 编译安装环境: yum -y install make gcc-c++ cmake ...
- Luffy之Xadmin以及首页搭建(轮播图,导航)
1. 首页 1.1 轮播图 admin站点配置支持图片上传 pip install Pillow 默认情况下,Django会将上传的图片保存在本地服务器上,需要配置保存的路径.我们可以将上传的文件保存 ...