大家好,欢迎来到IT知识分享网。
虚析构和纯虚析构原理
直接上代码:请根据编号查看代码说明。
先总结:
-
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
-
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
-
拥有纯虚析构函数的类也属于抽象类
animal类
//3.
class animal
{
public:
animal() { cout << "animal构造函数调用" << endl; }
//能调用子类析构函数 来释放堆区的解决方法:
//利用虚析构可以解决父类指针释放子类对象时不干净的问题
//virtual ~animal() { cout << "animal虚析构函数调用" << endl; }
//而如果想避免基类实例化,则可以将析构函数写成纯虚析构
//但因为派生类(子类)不可能来实现基类(父类)的析构函数,
//所以基类析构函数虽然可以标为纯虚,但是仍必须实现析构函数,
//否则派生类无法继承,也无法编译通过。
//纯虚析构 要声明也要实现(类外定义)
//有了纯虚析构之后,这个类也属于抽象类,并无法实例化对象
virtual ~animal() = 0;
virtual void speak() = 0;
};
//纯虚析构函数要类外定义
animal::~animal() { cout << "animal纯虚析构函数调用" << endl; }
cat类,里面继承了父类animal
// 4.
class cat : public animal
{
public:
cat(string name)
{
cout << "cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak() { cout << *m_Name << "小猫在说话" << endl; }
~cat()
{
if (m_Name != NULL)
{
cout << "cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
// 5.
void te01()
{
//父类指针指向了一个开辟在堆区的子类数据
animal* a = new cat("Tom");
//new一个在堆区的cat类型的对象,cat继承自animal
//我们知道创建子类时会先创建父类
//所以先调用了animal的构造函数
//再是cat的构造函数
//亿点细节:
//在创建父类时,如果发现父类中有虚函数
//编译器就会使用一个叫虚函数表的东西,保存所有的虚函数,包括纯虚函数
//在这里,animal父类的虚函数表中,有两个虚函数
/*
class animal size(4):
+---
0 | (vfptr)
+---
animal::$vftable@:
| &animal_mata
| 0
0 | &animal::(dtor)
1 | &animal::speak
*/
//&animal::(dtor) 就是animal的纯虚析构函数的地址
//dtor是destructor的缩写,是析构函数的意思,中文直译叫"垃圾焚毁炉"
//&animal::speak 就是speak虚函数的地址
//接着,子类继承了这个表,重写了speak。
/*
class cat size(8):
+---
0 | +--- (base class animal)
0 | | {vfptr}
| +---
4 | m_Name
+---
cat::$vftable@:
| &cat_meta
| 0
| &cat::(dtor)
0 | &cat::speak
*/
//这个dtor我不知道算不算重写,我觉的应该是的
//但我不了解原理,所以搬了一个别人的解释:
//当父类析构函数定义为虚函数后,子类默认就是虚析构函数,跟普通成员函数一样
//现在将父类析构函数 定义为虚函数,子类再继承了 父类的虚函数表
//子类的虚函数表中,就存在父类的 虚析构函数的地址,
//但子类的析构函数 也是虚函数,所以重写了自己虚函数表中 父类虚析构函数的地址
//变成了子类的虚析构函数地址
//虽然父子的析构函数名字不一样,但是他们占同一个坑
//(即父子析构函数在虚函数表中的位置是一样的,否则就不存在多态了)
//用父类指针指向子类,并最后通过父类指针删除子类对象的时候,
//发现animal类的析构函数是虚函数,就到子类对象的虚函数表里找析构函数
//此时就会调用虚函表中 子类的虚析构函数 并很好的防止了内存泄漏
//然后还有个问题,
//既然子类的虚函数表中只有子类虚析构函数,那它怎样析构父类呢?
//父类的析构函数又在哪呢?
//其实在子类的析构函数中,底层包含着对父类析构函数的调用
//原因我认为是:
//对象都存放在栈中,创建子类对象先调用父类的对象,
//父类对象压入栈,然后是子类对象入栈,由于栈是先进后出,
//delete父类的时候就先调用子类对象,在调用父类对象
a->speak();
//因为子类的speak重写了父类的speak,父类指针调用的是子类的speak
delete a;
//父类指针在析构的时候 不会调用子类中的析构函数
//导致子类如果有堆区属性,出现内存泄漏
//原因是指针a是animal类型的指针,也就是父类类型的指针
//释放a时只进行animal类的析构函数。
//但父类析构函数变成虚析构之后,这个问题就解决了
}
// 1.
int main()
{
// 2.
te01();
return 0;
}
本人才疏学浅,若有疏漏和错误,请指正 2892870137@qq.com
欢迎拜访我的主博客:https://h-james.github.io/
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/10818.html