虚函数作用:动态绑定,实现多态效果。

  场景问题:

    派生类中有资源需要回收,而在编程中采用多态,由基类的指针指向派生类,则在释放的时候,如果基类的析构函数不是virtual,则派生类的析构函数得不到释放

  总结:

  C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。

  如果静态类型绑定的指针,即指针的静态类型就是“派生类”本身,则析构的时候回先调用派生累析构,然后调用基类析构。

//基类,析构不是virtual
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_;
}; //测试
int main() {
Derived1* d1 = new Derived1();
Derived2 d2 = Derived2("Bob");
delete d1;
return ;
}

d1为Derived1类的指针,它指向一个在堆上创建的Derived1的对象;d2为一个在栈上创建的对象。其中d1所指的对象需要我们显式的用delete调用其析构函数;d2对象在其生命周期结束时,系统会自动调用其析构函数。看下其运行结果:

Base基类的析构函数并不是虚析构函数,现在结果显示,派生类的析构函数被调用了,正常的释放了其申请的内存资源。

  这两者并不矛盾,因为无论是d1还是d2,两者都属于静态绑定,而且其静态类型恰好都是派生类,因此,在析构的时候,即使基类的析构函数为非虚析构函数,也会调用相应派生类的析构函数

  当发生动态绑定时,也就是当用基类指针指向派生类,这时候采用delete显式删除指针所指对象时,如果Base基类的析构函数没有virtual,会发生什么情况?

  

int main() {
Base* base[] = {
new Derived1(),
new Derived2("Bob")
};
for (int i = ; i != ; ++i) {
delete base[i];
}
return ;
}

从上面结果我们看到,尽管派生类中定义了析构函数来释放其申请的资源,但是并没有得到调用。原因是基类指针指向了派生类对象,而基类中的析构函数却是非virtual的,之前讲过,虚函数是动态绑定的基础。现在析构函数不是virtual的,因此不会发生动态绑定,而是静态绑定,指针的静态类型为基类指针,因此在delete时候只会调用基类的析构函数,而不会调用派生类的析构函数。这样,在派生类中申请的资源就不会得到释放,就会造成内存泄漏,这是相当危险的:如果系统中有大量的派生类对象被这样创建和销毁,就会有内存不断的泄漏,久而久之,系统就会因为缺少内存而崩溃。

因此,为了防止这种情况下内存泄漏的发生,最好将基类的析构函数写成virtual虚析构函数

class Base {
public:
virtual ~Base() {
cout << "~Base()" << endl;
}
};

这样就会实现动态绑定,派生类的析构函数就会得到调用,从而避免了内存泄漏。

endl;

基类的析构函数写成virtual虚析构函数的更多相关文章

  1. C++中基类的析构函数为什么要用virtual虚析构函数

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  2. C++-基类的析构函数为什么要加virtual虚析构函数(转)

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  3. 【C++】C++中基类的析构函数为什么要用virtual虚析构函数?

    正面回答: 当基类的析构函数不是虚函数,并且基类指针指向一个派生类对象,然后通过基类指针来删除这个派生类对象时,如果基类的析构函数不是虚析构函数,那么派生类的析构函数就不会被调用,从而产生内存泄漏 # ...

  4. C++虚复制构造函数,设置Clone()方法返回基类指针,并设置为虚函数

    构造函数不能是虚函数.但有时候确实需要能传递一个指向基类对象的指针,并且有已创建的派生类对象的拷贝.通常在类内部创建一个Clone()方法,并设置为虚函数. //Listing 12.11 Virtu ...

  5. [转]C# 将类的内容写成JSON格式的字符串

    将类的内容写入到JSON格式的字符串中 本例中建立了Person类,赋值后将类中内容写入到字符串中 运行本代码需要添加引用动态库Newtonsoft.Json 程序代码: using System; ...

  6. Android 线程池系列教程(2)Thread,Runnable是基类及如何写Run方法

    Specifying the Code to Run on a Thread 上一课   下一课 1.This lesson teaches you to Define a Class that Im ...

  7. C++中基类虚析构函数的作用及其原理分析

    虚析构函数的理论前提是 执行完子类的析构函数,那么父类的虚构函数必然会被执行. 那么当用delete释放一个父类指针所实例化的子类对象时,如果没有定义虚析构函数,那么将只会调用父类的析构函数,而不会调 ...

  8. 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数

    1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...

  9. C++ Pirmer : 第十五章 : 面向对象程序设计之基类和派生的定义、类型转换与继承与虚函数

    基类和派生类的定义以及虚函数 基类Quote的定义: classs Quote { public: Quote() = default; Quote(cosnt std::string& bo ...

随机推荐

  1. Python.URLs

    1. The Future of Asynchronous IO in Python https://medium.com/@paulcolomiets/the-future-of-asynchron ...

  2. vs2015 npm list 更新问题

    在更新npm list时候,经常会非常的慢,今天试了一个诡异的方法,就是在文件夹下面直接把所有缓存全部删除,全部重新下,结果感觉反而速度快很多. 原来的更新包80M竟然1个小时没有下载完. C:\Us ...

  3. APM浅析

    APM(Application Performance Management & Monitoring)一种基于云的性能监控服务(SaaS),以非侵入式监听探针,收集应用关键指标,生成分析报表 ...

  4. laravel错误1071 Specified key was too long; max key length is 1000 bytes

    Laravel 5.5 环境,php artisan migrate 之后,出现错误如题. 检查了一下,代码是这样的: $table->increments('id'); $table-> ...

  5. centos php 运行环境搭建

    一.安装apache httpd rpm -qa|grep httpd //检查是否安装apache rpm -e 包名 --nodeps //若有则删除 PS:我没有删除,直接用的服务器原来的. y ...

  6. android 使用UDP发送数据 DatagramSocket 创建对象为null

    DatagramSocket socket=null; try { socket = new DatagramSocket();  //这里创建对象为空 } catch (SocketExceptio ...

  7. 2017/2/7utf-8与GBK的区别与修改

    1.GBK:是中文字符编码 2.UTF-8:是国际编码 3.使用GBK与UTF-8编码更耗内存,同时有英文字符多的 尽量用UTF-8编码 4.在项目中,几个修改字符串的方法:

  8. Java第1章笔记

    第一章 计算机程序:计算机为完成某些功能产生的一系列有序指令集合. Java技术包括:JavaSE(标准版)  JavaEE(企业版) ---JavaME(移动版) 开发Java程序步骤:1.编写 2 ...

  9. Java:斐波那契数列

    斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10 ...

  10. kbmmw 的HTTPSmartService中的跨域访问

    有同学在使用kbmmw 与extjs 结合的时候,涉及到了跨域访问,这个在 kbmmw 里面已经完全解决. extjs 在访问跨域的时候,首先会使用OPIONS  调用,服务端要根据浏览器请求的 he ...