-
2020-07-05 10:31:05
1、为什么要使用虚析构函数
我们知道析构函数是在对象生命周期结束时自动被调用,用来做一些清理工作(如释放句柄,释放堆内存等),防止出现内存泄漏。
那怎么还有虚析构函数呢?
使用虚析构函数的类一般是要作为基类,被其他类继承。通过把基类的析构函数声明为虚函数,就可以通过父类指针来释放子类对象,从而完成子类的一些清理工作,防止出现内存泄漏。
案例1:基类析构函数为非虚函数
//test.h class Parent { public: Parent(); ~Parent(); private: int *p_ptr; }; class Child : public Parent { public: Child(); ~Child(); private: int *c_ptr; };
//test.cpp #include "test.h" #include <iostream> using namespace std; Parent::Parent() { p_ptr=new int; *p_ptr=10; } Parent::~Parent() { cout << "Parent::~Parent() was called." << endl; if(p_ptr != 0) { delete p_ptr; p_ptr=0; } } Child::Child() { c_ptr=new int; *c_ptr=20; }
//main.cpp #include "test.h" void func(Parent *parent) { delete parent;//通过父类指针来释放子类对象 } int main(int argc, char *argv[]) { Child *child=new Child; func(child); return 1; }
运行结果:
Parent::~Parent() was called.
结论:父类析构函数为非虚函数时,通过父类指针来释放子类对象时,只会调用父类的析构函数,而不会调用子类的析构函数,造成了子类的内存泄漏。所以,应该将父类的析构函数声明为虚函数。案例2:父类的析构函数为虚函数
其他文件不用动,只需修改test.h,将父类的析构函数声明为虚函数。
//test.h class Parent { public: Parent(); virtual ~Parent();//虚析构函数 private: int *p_ptr; }; class Child : public Parent { public: Child(); ~Child(); private: int *c_ptr; };
运行结果:
Child::~Child() was called.
Parent::~Parent() was called.
结论:只有将父类的析构函数声明为虚析构函数时,通过父类指针释放子类对象时,会先调用子类的析构函数,然后调用父类的析构函数,不存在内存泄漏问题。2、纯虚析构函数
通过上面的虚析构函数知道,C++基类的析构函数最好声明为虚机构函数,那什么时候声明为纯虚析构函数呢?
我们知道,带有纯虚函数的类为抽象类,不能被实例化,只能被子类继承,所以当我们设计一个基类为抽象类时,可以把析构函数声明为纯虚析构函数,这样基类就是抽象类了。
注意:纯虚析构函数也要有函数体,用来做一些基类的清理工作,防止基类出现内存泄漏。
更多相关内容 -
虚析构和纯虚析构函数
2020-12-08 18:21:46解决方式:将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构共性: 1,可以解决父类指针释放子类对象 2,都需要有具体的函数实现 虚析构和纯虚析构区别: 如果时纯虚析构,则该类属于抽象类,无法实例化...多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码,则导致内存泄漏。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1,可以解决父类指针释放子类对象
2,都需要有具体的函数实现
虚析构和纯虚析构区别:
如果时纯虚析构,则该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名() { … }
纯虚析构语法:
virtual ~类名() = 0;
类名:: ~类名() { …}#include <iostream> #include <string> using namespace std; class Animal { public: //只要有一个纯虚函数,这个函数称为抽象类 //抽象类特点:1、无法实例化对象 2、抽象类的子类必须要重写父类的纯虚函数 Animal() { cout << "Animal构造函数调用" << endl; } //使用虚析构可以解决 父类指针释放子类对象时不干净的问题 virtual ~Animal() { cout << "Animal虚析构函数调用" << endl; } //纯虚析构函数 1、需要声明也需要实现 2、有了纯虚析构之后,这个类也属于抽象类,无法实例化 // virtual ~Animal() = 0; virtual void speak() = 0; //纯虚函数 }; //Animal::~Animal() //{ // cout << "Animal纯虚析构函数调用" << endl; //} class Cat : public Animal { public: Cat(string name) { cout << "Cat构造函数调用" << endl; m_Name = new string(name); //开辟到堆区 } ~Cat() { if (m_Name != NULL) { cout << "Cat析构函数调用" << endl; // m_Name = NULL; delete m_Name; } } virtual void speak() { cout << *m_Name << "小猫在说话" << endl; } string *m_Name; //开辟到堆区 }; void test01() { Animal *animal = new Cat("Tom"); animal->speak(); delete animal; } int main() { test01(); system("pause"); return 0; }
-
6.4虚析构函数和纯虚析构函数
2021-05-19 19:07:46虚析构函数和纯虚析构函数虚析构函数和纯虚析构函数
如果有一定基础的伙伴来看这篇文章之前应该都知道虚析构函数的用途,虚析构函数就是防止有有没有释放干净的内存,防止内存泄漏。
没学过也没有关系我们通过了解原理的过程来学习这个虚析构和纯虚析构函数。
首先字面意思,分开理解,先看虚析构函数,析构函数就是释放内存的东西,虚函数,第六章我们都没离开这个东西,所以我们可以和虚表联系起来。那就是有虚表的析构函数吧。(纯虚析构函数我们最后再讲)
通过画图来理解我们的整个过程。(最近发现画图是真好用,形象)
最开始我们来探究我们没有虚析构函数的代码,然后再来对比有虚函数的代码,对比了才明显
先贴上我们要的没有虚析构函数刨析的代码:
#include<iostream> using namespace std; class A { public: A() { cout << "A中构造函数的调用" << endl; p_A = new int(666); } ~A() { if (p_A != NULL) { cout << "A中析构函数的调用" << endl; delete(p_A); p_A = NULL; } } int* p_A; }; class B:public A { public: B() { cout << "B中构造函数的调用" << endl; p_B = new int(999); } ~B() { if (p_B != NULL) { cout << "B中析构函数的调用" << endl; delete(p_B); p_B = NULL; } } int* p_B; }; int main() { A* a = new B; delete(a); }
看看运行的结果:
很明显少了一次我们对B的析构函数,但是我们的B在堆区开辟了一个空间,所以我们造成了内存泄漏。那么这个过程是怎么样的呢?
第一个过程:
第二个过程:
我们都知道,我们应该先释放子类,然后再释放父类吧。关于原理前面文章讲过这里不细说。
形象比喻:有了爸爸才有儿子,儿子才刚刚生下来,坑定不能爸爸就挂了吧,所以先挂儿子,再挂爸爸。
到这里就没有然后了,程序就结束了,很明显,我们new的p_B还没有被释放,所以造成了内存的泄漏,那么我们又来看看为什么加上了virtual又能解决这个问题?
上新代码:
#include<iostream> using namespace std; class A { public: A() { cout << "A中构造函数的调用" << endl; p_A = new int(666); } virtual ~A() { if (p_A != NULL) { cout << "A中析构函数的调用" << endl; delete(p_A); p_A = NULL; } } int* p_A; }; class B:public A { public: B() { cout << "B中构造函数的调用" << endl; p_B = new int(999); } virtual ~B() { if (p_B != NULL) { cout << "B中析构函数的调用" << endl; delete(p_B); p_B = NULL; } } int* p_B; }; int main() { A* a = new B; delete(a); }
调用析构函数的时候都先查表,然后找到自己相应的析构函数然后调用。
什么是纯虚析构函数呢?
纯虚构析构函数 就是纯虚函数加上析构函数,一般我们把函数设置纯虚函数都是不想这个类实例化,抽象出来的顶层父类,并且这个纯虚函数不能实现。 但是在纯虚析构这里不同 因为析构函数的调用顺序是派生类 成员类 基类,就算你基类是纯虚函数,编译器还是会产生对他的调用,所以要保证为纯虚析构提供函数体,如果你不做有的编译器会自动加上,但是有的编译器却会出现问题。如果不提供该析构函数的实现,将使得在析构过程中,析构无法完成而导致析构异常的问题。
这里面有一个误区:有人认为,virtual f()=0这种纯虚函数语法就是没有定义体的语义。其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 。通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。
纯虚析构需要注意的地方就是必须要提供函数体,狭隘的理解:
我们每次用虚析构函数 都是为了释放开辟出来的内存吧,如果我们的纯虚析构函数没有实现的话那么就无法发释放基类中开辟出来的内存空间。为了不把大家弄混淆,不在这篇文章讨论另一个题外话下一篇文章我们来讨论:
假如我们没有在每个析构函数面前都加virtual会发生什么情况,分情况讨论。
假如我们就是B类本身类型接收,再加上virtual又会发生什么? -
虚析构和纯虚析构函数去解决子类开辟在堆区的空间释放不干净的问题
2021-08-01 17:42:37本篇文章讲解利用虚析构和纯虚析构函数去解决子类开辟在堆区的空间,释放子类开辟在堆区的空间,纯虚析构函数和虚析构的共同特点:必须要写实现,纯虚析构函数,类外写实现,虚析构函数类内写实现,都可以通过父类...本篇文章讲解利用虚析构和纯虚析构函数去解决子类开辟在堆区的空间,释放子类开辟在堆区的空间,纯虚析构函数和虚析构的共同特点:必须要写实现,纯虚析构函数,类外写实现,虚析构函数类内写实现,都可以通过父类对象去释放子类开辟的空间(new出来的空间,开辟在堆区)
1:
代码如下:#include<iostream> #include<string> using namespace std; //抽象类 class father { public: //纯虚函数 father() { cout << "father构造函数的调用!" << endl; } //纯虚函数不需要类外实现 virtual void func() = 0; 利用虚析构可以解决 父类指针释放子类对象不干净的问题 //virtual ~father()//未加virtual之前无法通过父类对象清除子类开辟在堆区的空间 //{ // cout << "father析构函数的调用!" << endl; //} //有了纯虚析构,该类也属于抽象类,无法实例化对象 virtual ~father() = 0;//纯虚析构函数必须在类外写实现,否则运行的时候报错,必须要走的,所以要写实现 }; //类外写的纯虚析构记得加作用域 father::~father() { cout << "father纯虚析构析构函数的调用!" << endl; } class son :public father { public: void func() { cout <<*M_name<< "虚函数的调用!" << endl; } string *M_name; son(string name) { cout << "子类构造函数的调用!" << endl; M_name = new string(name);//new关键字开辟的空间需要用指针去接收 } ~son() { if (M_name != NULL) { cout << "子类析构函数的调用!" << endl; delete M_name; M_name = NULL; } } }; void func() { father *f = new son("wan");//父类为抽象类,无法实例化对象,但是可以通过new一个子类的对象进行实例化,用指针接收 f->func(); delete f; } int main() { func(); system("pause"); return 0; }
virtual关键字的使用能够通过父类对象去访问子类的空间,即实现多态一定要写子类重写父类的虚函数,而虚析构和纯虚析构的使用可以进一步通过多态的方式去释放子类开辟在堆区的空间。
-
C++虚析构函数、纯虚析构函数
2021-01-20 03:36:16虚析构函数 析构函数的工作方式是:底层的派生类(most derived class)的析构函数先被调用,然后调用每一个基类的析构函数。 因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非... -
析构函数、虚析构函数、纯虚析构函数
2021-03-12 19:12:01这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生; 普通析构函数 CBase是基类,CDerive是其子类,类源码代码如下: ... -
虚析构和纯虚析构
2020-06-09 08:29:28解决:将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构 共性:1.可以解决父类指针释放子类对象 2.都需要有具体的函数实现 区别:如果是纯虚析构,该类属于抽象类,无法实例化对象 虚析构语法 ... -
简述虚析构函数与纯虚析构函数
2019-03-25 19:56:51基础知识: C++中,一个类在建成时...即使析构函数不被声明,也会隐式调用析构函数。 那么,当一个派生类继承了一个基类,这时候调用析构函数,会发生什么呢? #include <iostream> using namespace std;... -
虚析构和纯虚析构原理
2020-07-15 14:29:30拥有纯虚析构函数的类也属于抽象类 animal类 //3. class animal { public: animal() { cout << "animal构造函数调用" << endl; } //能调用子类析构函数 来释放堆区的解决方法: //利用虚析构... -
c++虚析构和纯虚析构
2020-12-26 10:51:26解决方式:将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构共性: 可以解决父类指针释放子类对象 都需要有具体的函数实现 虚析构和纯虚析构区别: 如果是纯虚析构,该类属于抽象类,无法实例化对象 ... -
C++面向对象-26-虚析构和纯虚析构
2020-05-18 23:28:09这篇来学习多态中可能会发生内存泄漏和解决办法,就要使用到虚析构函数和纯虚析构函数。先不介绍概念,肯定和前面学构造函数和析构函数中的析构函数有关系。先通过引出问题,然后介绍这两个概念和特点。 1.多态... -
C++_类和对象_C++多态_虚析构和纯虚析构函数---C++语言工作笔记074
2021-06-16 20:21:07然后我们再来看,虚析构和纯虚析构函数的用法,上面有大体的介绍了. 但是我们还是用一个例子来说明一下更好. 首先,我们去创建一个Animal类,类中我们去写上一个spreak这个,纯虚函数,然后,让我们的 Animal类,变成一... -
C++基础(二十)虚析构函数、纯虚析构函数
2020-10-03 22:16:14话不多说上代码 #include <iostream> using namespace std; class Animal { public: Animal() { cout << "ani的构造器" <... //第一种写法 虚析构的写法 ... "ani的析构函数 -
C++ 虚析构函数和纯虚析构函数
2020-08-01 11:54:43虚析构函数和纯析构函数 1.当父类指针或引用指向子类对象时,若子类中数据成员有在堆区申请空间,父类析构函数需要 声明为虚析构函数或纯析构函数,析构时子类的析构函数会被调用,堆区空间会被释放,否则子类对象会... -
C++中虚析构函数和纯虚析构函数
2020-03-16 16:29:57为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。 class Base { public: Base(){} virtual ~Bas.... -
C++中的虚析构函数、纯虚析构函数详解
2015-06-08 22:03:48C++中析构函数可以为纯虚函数吗?众所周知,在实现多态的过程中,一般将基类的析构函数设为virtual,以便在delete的时候能够多态的链式调用。那么析构函数是否可以设为纯虚呢?class CBase { public: CBase() { ... -
C++面向对象多态之虚析构函数和纯虚函数
2020-07-10 17:54:22delete父类指针(如下面代码的cat指针)时,才会调用子类的析构函数,保证析构的完整性 #include <iostream> using namespace std; struct Animal { virtual void speak() { cout << "Animal::speak... -
C++中虚析构函数和纯虚函数的作用
2017-12-26 23:38:10虚析构函数为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。class Base { public: Base(){} ... -
C++中的纯虚析构函数
2020-04-03 11:30:11记住最重要的事情之一,如果类包含纯虚析构函数,则必须为纯虚析构函数提供函数体。 为什么需要函数体呢?原因是因为析构函数(与其它函数不同)实际上并未被 “重写”,而是始终以派生类的相反顺序调用它们。这意味... -
纯虚析构函数和非纯虚析构函数
2017-05-03 14:14:04纯虚机构函数和非纯虚析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。 纯虚析构函数的特点:必须为纯虚析构函数提供一个函数体。 -
纯虚析构函数的使用
2021-02-23 12:49:22就是在类完定义一下,不过这样就不是纯虚析构函数了,析构函数和纯虚是矛盾的,这个地方也涉及了另外一个知识点,就是类内static变量初始化的问题,除了static const int 可以在类内初始化完,其他的都不可以在类内... -
C++学习笔记之虚析构和纯虚析构/虚析构和纯虚析构的学习/虚析构和纯虚析构学习
2020-08-03 09:57:43解决办法: 将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构共性: 可以解决父类指针释放子类对象 都需要有具体的函数实现 虚析构和纯虚析构区别: 如果是纯虚析构,该类是抽象类,无法实例化对象。 虚... -
C++:虚析构和纯虚析构
2020-12-14 15:45:05一、虚析构 -若子类中存在指向堆区的属性,须利用虚析构技术(将父类析构函数写成虚函数),在delete时,才会调用子类的析构函数。 #include <iostream> #include <string> using namespace std; class... -
C++: 虚析构和纯虚析构
2020-12-27 23:36:41//3、纯虚析构函数 不仅需要声明 也需要实现(父类指针有可能在堆区) //4、有了纯虚析构函数 该类也属于 抽象类 无法实例化对象 //共性: //1、可以用 父类指针释放子类对象 //2、需要重写 子类的析构函数 //区别:...