對象的銷燬
作者 CodeAllen ,轉載請註明出處
1.對象的銷燬
生活中的對象都是被初始化後才上市的
生活中的對象被銷燬前會做一些清理工作
問題:C++中如何清理需要銷燬的對象?
一般而言,需要銷燬的對象都應該做清理
解決方案
- 爲每個類都提供一個public的free函數
- 對象不再需要時立即調用free函數進行清理
存在的問題 - free只是一個普通的函數,必須顯示的調用
- 對象銷燬前沒有做清理,很可能造成資源泄漏
- C++編譯器是否能夠自動調用某個特殊的函數進行對象的清理?
2.析構函數
C++的類中可以定義一個特殊的清理函數
- 這個特殊的清理函數叫做析構函數
- 析構函數的功能與構造函數相反
定義:~ClassName()
- 析構函數沒有參數也沒有返回值類型聲明
- 析構函數在對象銷燬時自動被調用
析構函數使用
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(): %d\n", mi);
}
~Test()
{
printf("~Test(): %d\n", mi);
}
};
int main()
{
Test t(1);
Test* pt = new Test(2);
delete pt;
return 0;
}
/*
從結果可以看出來,delete時候釋放了pt,函數return的時候釋放了t
Test(): 1
Test(): 2
~Test(): 2
~Test(): 1
*/
析構函數的定義準則
- 當類中自定義了析構函數,並且構造函數中使用了系統資源(如:內存申請,文件打開等),則需要自定義析構函數
小結
析構函數是對象銷燬時進行清理的特殊函數
析構函數在對象銷燬時自動被調用
析構函數是對象釋放系統資源的保障
臨時對象的概念
作者 CodeAllen ,轉載請註明出處
1.有趣的問題
下面的程序輸出什麼?爲什麼?
有趣的問題
#include <stdio.h>
class Test {
int mi;
public:
Test(int i) {
mi = i;
}
Test() {
Test(0); //等價於沒有,過了這一行就沒了
}
void print() {
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
t.print();
return 0;
}
2.發生了什麼?
程序意圖:
- 在Test()中以0作爲參數調用Test(int i)
- 將成員變量mi的初始值設置爲0
運行結果: - 成員變量mi的值爲隨機值
- 究竟哪個地方出了問題? 臨時對象的出現
3.思考
構造函數是一個特殊的函數
- 是否可以直接調用?
- 是否可以在構造函數中調用構造函數?
- 直接調用構造函數的行爲是什麼?
4.答案
- 直接調用構造函數將產生一個臨時對象
- 臨時對象的生命週期只有一條語句的時間
- 臨時對象的作用域只在一條語句中
- 臨時對象是C++中值得警惕的灰色地帶
解決方案
#include <stdio.h>
class Test {
int mi;
void init(int i) //私有的初始函數
{
mi = i;
}
public:
Test(int i) {
init(i); //用的時候調用,就不會產生臨時對象
}
Test() {
init(0);
}
void print() {
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
t.print();
return 0;
}
5.編譯器的行爲
現代C++編譯器在不影響最終執行結果的前提下,會盡力減少臨時對象的產生!!!
臨時對象
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
printf("Test(int i) : %d\n", i);
mi = i;
}
Test(const Test& t)
{
printf("Test(const Test& t) : %d\n", t.mi);
mi = t.mi;
}
Test()
{
printf("Test()\n");
mi = 0;
}
int print()
{
printf("mi = %d\n", mi);
}
~Test()
{
printf("~Test()\n");
}
};
Test func()
{
return Test(20);
}
int main()
{
Test t = Test(10); //預計運行過程: 1.生成臨時對象 2.用臨時對象初始化t對象
//==>調用拷貝構造函數,實際運行過程:==> Test t = 10; 推薦這麼寫就可以避免臨時對象
Test tt = func(); // ==> Test tt = Test(20); ==> Test tt = 20;
t.print();
tt.print();
return 0;
}
小結
直接調用構造函數將產生一個臨時對象
臨時對象是性能的瓶頸,也是bug的來源之一
現代C++編譯器會盡力避開臨時對象
實際工程開發中需要人爲的避開臨時對象