C++析构函数
- 知识点:栈清空顺序
- 知识点:派生类实例释放空间顺序
- 知识点:派生类实例在堆空间中的处理(析构函数)
- 知识点:构造函数的调用顺序
- 代码展示
代码
以下展示两个类,子类父类是派生的关系。
//code block 1
class Base
{
public:
Base () {}
~Base ()
{
cout << "Base Destructor" << endl;
//~Base();
}
};
class Derive : public Base
{
public:
Derive() {}
~Derive()
{
cout << "Derive Destructor" << endl;
//~Base();
}
};
一开始想写一个不涉及到指针的代码:
//code block 2
int main()
{
Base base;
Derive derive;
return 0;
}
想想输出的结果会是什么?
自己本来以为会这么输出(证明是错误的,先释放base再释放derive):
Base Destructor
Base Destructor
Derive Destructor
结果输出结果是这样的:
Derive Destructor
Base Destructor
Base Destructor
其实忽略了两个知识点:栈清空的顺序、派生类实例调用析构函数释放空间顺序。
知识点:栈清空的顺序
栈是不用coder去释放空间的。你只管用,只管吃。至于刷盘子洗碗做饭你不需要干。栈清空的顺序完全就是栈的特性——先进后出、后进先出。base先入栈、derive再入栈。释放空间的时候,栈顶元素首先弹栈、释放空间。然后再base释放空间。
栈 |
---|
derive |
base |
先释放derive,再释放base。我记得是父类首先调用析构函数释放空间、然后子类调用析构函数释放空间嘛,那输出可能是(证明是错误的,先调用子类析构函数):
Base Destructor
Derive Destructor
Base Destructor
知识点:调用析构函数顺序
派生类对象先调用自己的析构函数,然后再调用父类的析构函数。我觉得可以这么解释,先把属于子类的成员释放掉,再去释放父类的成员。
可以这么想(二级派生看得比较爽):
//code block 5
class GrandBase
{
public:
GrandBase() {}
~GrandBase()
{
cout << "GrandBase Destructor" << endl;
//NO father
}
};
class Base : public GrandBase
{
public :
Base(){}
Base::~Base()
{
cout << "Base Destructor" << endl;
//~GrandBase();
}
};
class Derive : public Base
{
public:
Derive() {}
~Derive()
{
cout << "Derive Destructor" << endl;
//~Base();
}
};
编译器自己在析构函数中加了调用父类析构函数的代码。
现在让我们回到code block 2中,我们把代码改成带有指针的。
//code block 3
int main()
{
Base *pBase = new Derive();
delete pBase;
pBase = nullptr;
return 0;
}
这回我们把变量空间放在了堆中。堆有一个特点:在堆中开辟的空间,你要自己去释放。如果不去释放,就等着软件进程死掉,操作系统一块回收。还拿吃饭来说,不光要吃、还要管做饭、洗碗。我们开辟了一个Derive对象,这个对象的内存里包括Derive的空间和Base的空间。如果我们最后手动释放内存的时候,不光要释放Base部分的还要释放Derive部分的。
那么code block 3输出的结果会是什么呢?
Base Destructor
我们发现只调用了Base的析构函数,也就是说只释放了Base的部分。和我们预想的不一样啊。为什么会这样呢?我们注意到pBase是Base 类型,也就是指向Base的指针,没人告诉它要调用Derive析构函数,它为什么要调用呢?你说这不是贱吗对吧!这就引出了“虚析构函数”:在父类析构函数前面加一个virtual。系统在运行时,就会利用多态,认识到:“这个pBase虽然长得像Base 但它不止Base对象这么简单,他是我儿子的实例,它释放空间的时候按照我儿子的方法去释放。”
class Base
{
public :
Base(){}
virtual ~Base();
};
Base::~Base()
{
cout << "Base Destructor" << endl;
}
在派生类的使用上父类不写成虚析构函数可能会内存泄露。一泻千里,雅美蝶!
知识点:构造函数的调用顺序
把code block 5代码稍加修改,我们研究一下构造函数的调用顺序
先看代码:
//code block 5
class GrandBase
{
public:
GrandBase()
{
//No Father
cout << "GrandBase Constructor" << endl;
}
~GrandBase()
{
cout << "GrandBase Destructor" << endl;
//NO father
}
};
class Base : public GrandBase
{
public :
Base()
{
//GrandBase();
cout << "Base Constructor" << endl;
}
Base::~Base()
{
cout << "Base Destructor" << endl;
//~GrandBase();
}
};
class Derive : public Base
{
public:
Derive()
{
//Base ();
cout << "Derive Constructor" << endl;
}
~Derive()
{
cout << "Derive Destructor" << endl;
//~Base();
}
};
int main()
{
//我这么写
Derive *pDerive = new Derive ();
delete pDerive ;
pDerive = nullptr;
}
会输出什么?
GrandBase Constructor
Base Constructor
Derive Constructor
Derive Destructor
Base Destructor
GrandBase Destructor
这回对了,哈哈。
一样可以理解为编译器加了几段代码。