C++中基类虚析构函数的作用及其原理分析
虚析构函数的理论前提是
执行完子类的析构函数,那么父类的虚构函数必然会被执行。
那么当用delete释放一个父类指针所实例化的子类对象时,如果没有定义虚析构函数,那么将只会调用父类的析构函数,而不会调用子类的虚构函数,导致内存的泄漏。
故: 继承时,要养成的一个好习惯就是,基类析构函数中,加上virtual。
知识背景
要弄明白这个问题,首先要了解下C++中的动态绑定。
关于动态绑定的讲解,请参阅: C++中的动态类型与动态绑定、虚函数、多态实现
正题
直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
示例代码讲解
现有Base基类,其析构函数为非虚析构函数。Derived1和Derived2为Base的派生类,这两个派生类中均有以string* 指向存储其name的地址空间,name对象是通过new创建在堆上的对象,因此在析构时,需要显式调用delete删除指针归还内存,否则就会造成内存泄漏。
class Base {
public:
~Base() {
cout << "~Base()" << endl;
}
};
class Derived1 : public Base {
public:
Derived1():name_(new string("NULL")) {}
Derived1(const string& n):name_(new string(n)) {}
~Derived1() {
delete name_;
cout << "~Derived1(): name_ has been deleted." << endl;
}
private:
string* name_;
};
class Derived2 : public Base {
public:
Derived2():name_(new string("NULL")) {}
Derived2(const string& n):name_(new string(n)) {}
~Derived2() {
delete name_;
cout << "~Derived2(): name_ has been deleted." << endl;
}
private:
string* name_;
};
我们看下面对其析构情况进行测试:
d1为Derived1类的指针,它指向一个在堆上创建的Derived1的对象;d2为一个在栈上创建的对象。其中d1所指的对象需要我们显式的用delete调用其析构函数;d2对象在其生命周期结束时,系统会自动调用其析构函数。看下其运行结果:

刚才我们说,Base基类的析构函数并不是虚析构函数,现在结果显示,派生类的析构函数被调用了,正常的释放了其申请的内存资源。这两者并不矛盾,因为无论是d1还是d2,两者都属于静态绑定,而且其静态类型恰好都是派生类,因此,在析构的时候,即使基类的析构函数为非虚析构函数,也会调用相应派生类的析构函数。
下面我们来看下,当发生动态绑定时,也就是当用基类指针指向派生类,这时候采用delete显式删除指针所指对象时,如果Base基类的析构函数没有virtual,会发生什么情况?
int main() {
Base* base[2] = {
new Derived1(),
new Derived2("Bob")
};
for (int i = 0; i != 2; ++i) {
delete base[i];
}
return 0;
}

从上面结果我们看到,尽管派生类中定义了析构函数来释放其申请的资源,但是并没有得到调用。原因是基类指针指向了派生类对象,而基类中的析构函数却是非virtual的,之前讲过,虚函数是动态绑定的基础。现在析构函数不是virtual的,因此不会发生动态绑定,而是静态绑定,指针的静态类型为基类指针,因此在delete时候只会调用基类的析构函数,而不会调用派生类的析构函数。这样,在派生类中申请的资源就不会得到释放,就会造成内存泄漏,这是相当危险的:如果系统中有大量的派生类对象被这样创建和销毁,就会有内存不断的泄漏,久而久之,系统就会因为缺少内存而崩溃。
也就是说,在基类的析构函数为非虚析构函数的时候,并不一定会造成内存泄漏;当派生类对象的析构函数中有内存需要收回,并且在编程过程中采用了基类指针指向派生类对象,如为了实现多态,并且通过基类指针将该对象销毁,这时,就会因为基类的析构函数为非虚析构函数而不触发动态绑定,从而没有调用派生类的析构函数而导致内存泄漏。
因此,为了防止这种情况下内存泄漏的发生,最好将基类的析构函数写成virtual虚析构函数。
下面把Base基类的析构函数改为虚析构函数:
class Base {
public:
virtual ~Base() {
cout << "~Base()" << endl;
}
};

这样就会实现动态绑定,派生类的析构函数就会得到调用,从而避免了内存泄漏。
本文转载于:https://www.cnblogs.com/liushui-sky/p/5824919.html
C++中基类虚析构函数的作用及其原理分析的更多相关文章
- C++中基类的析构函数为什么要用virtual虚析构函数
知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅: C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...
- 【C++】C++中基类的析构函数为什么要用virtual虚析构函数?
正面回答: 当基类的析构函数不是虚函数,并且基类指针指向一个派生类对象,然后通过基类指针来删除这个派生类对象时,如果基类的析构函数不是虚析构函数,那么派生类的析构函数就不会被调用,从而产生内存泄漏 # ...
- 虚函数的使用 以及虚函数与重载的关系, 空虚函数的作用,纯虚函数->抽象类,基类虚析构函数使释放对象更彻底
为了访问公有派生类的特定成员,可以通过讲基类指针显示转换为派生类指针. 也可以将基类的非静态成员函数定义为虚函数(在函数前加上virtual) #include<iostream> usi ...
- 构造函数为什么不能为虚函数 & 基类的析构函数为什么要为虚函数
一.构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的.问题出来了,假设构造函数 ...
- 基类的析构函数写成virtual虚析构函数
虚函数作用:动态绑定,实现多态效果. 场景问题: 派生类中有资源需要回收,而在编程中采用多态,由基类的指针指向派生类,则在释放的时候,如果基类的析构函数不是virtual,则派生类的析构函数得不到释放 ...
- C++-基类的析构函数为什么要加virtual虚析构函数(转)
知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅: C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...
- C++虚析构函数的作用
注:本文内容来源于zhice163博文,感谢作者的整理. 1.为什么基类的析构函数是虚函数? 在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生. 下面转自网络:源地址 ...
- C++多态性中基类析构函数声明为虚函数
在用基类指针指向派生类时, 在基类析构函数声明为virtual的时候,delete基类指针,会先调用派生类的析构函数,再调用基类的析构函数. 在基类析构函数没有声明为virtual的时候,delete ...
- 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数
1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...
随机推荐
- web前端----jQuery操作标签
样式操作 样式类 addClass();// 添加指定的CSS类名. removeClass();// 移除指定的CSS类名. hasClass();// 判断样式存不存在 toggleClass() ...
- 在python3下使用OpenCV 显示图像
在Python3下用使用OpenCV比在C,C++里开发不止快捷一点点, 原型开发的时候蛮有用. 这里用的OpenCV 加载图片, 用的imshow画图 # -*- coding: utf-8 -*- ...
- python的re正则表达式模块
元字符 . * + ? ^ $ { } [ ] - \ . 匹配除了/n之外的任意一个字符 * 匹配*前面的单个字符任意次,即[0,+∞] + 匹配 ...
- CSS实现三角形、梯形、平行四边形、圆形、椭圆形、对话框、自适应正方形
本文篇幅较长,希望能坚持看完,转载请注明出处,如果觉得好文请给个赞吧 CSS实现梯形 CSS实现三角形和梯形主要是依靠border是梯形的特性来做的,有点像相框的那种感觉. 首先我们先给一个正方形设置 ...
- UVa 10635 Prince and Princess - 动态规划
讲一下题目大意,就是有两个长度为p + 1和q + 1的序列,求它们的LCS. 如果用O(pq)的算法对于这道题来说还是太慢了.所以要另外想一些方法.注意到序列中的所有元素都不相同,所以两个序列中数对 ...
- [c/c++]指针(4)
现在讲一下指针的主要用途和常见错误. [用途] 1.用作形参 首先,常见新手写交换函数: void swap(int a,int b){ int t = a; a = b; b = t; } 主函数里 ...
- java在访问https资源时的证书信任问题
java程序在访问https资源时,出现报错 sun.security.validator.ValidatorException: PKIX path building failed: sun.sec ...
- C# string字节数组转换
string转byte[]:byte[] byteArray = System.Text.Encoding.Default.GetBytes ( str ); byte[]转string:string ...
- Git 基础 —— 安装 配置 别名 对象
Git 基础学习系列 Git 基础 -- 安装 配置 别名 对象 Git 基础 -- 常用命令 Git 基础 -- 常见使用场景 Git基础 -- Github 的使用 Git 安装 Git下载地址 ...
- 分布式系统一致性协议--2PC,3PC
分布式系统中最重要的一块,一致性协议,其中就包括了大名鼎鼎的Paxos算法. 2PC与3PC 在分布式系统中,每一个机器节点虽然能够明确知道自己在进行事务操作过程中的结果是成功或是失败,但是却无法直接 ...