c++解構函式三分法則
對於新手來說,解構函式常常漏掉,導致記憶體洩漏
這裡寫的還不夠完整,請多參考相關書籍
設計一個類時,如何寫解構函式?
解構函式如果我們不寫的話,C++ 會幫我們自動的合成一個,就是說:C++ 會自動的幫我們寫一個解構函式。很多時候,自動生成的解構函式可以很好的工作,但是一些重要的事蹟,就必須我們自己去寫解構函式。
解構函式和建構函式是一對。建構函式用於建立物件,而解構函式是用來撤銷物件。簡單的說:一個物件出生的時候,使用建構函式,死掉的時候,使用解構函式。
下面我們來做一個例子,看看:
#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
NoName():pstring(new std::string), i(0), d(0){}
private:
std::string * pstring;
int i;
double d;
};
int main(){
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
像上面這個 NoName
類這樣的設計,類裡面有一個成員變數是指標(std::string *pstring
) ,那麼在建構函式裡我們使用 new
建立了物件,並使用 pstring
來操作這個物件。那麼在這個情況下,我們就必須設計一個解構函式。
解構函式是這樣編寫的:(可以在類的裡面宣告,定義寫在類的外面,)
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "建構函式被呼叫了!" << endl;
}
~NoName();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
NoName::~NoName(){
cout << "解構函式被呼叫了!" << endl;
}
- 1
- 2
- 3
解構函式是這樣寫的: ~NoName()
,它與建構函式唯一的區別就是,前面都加了一個 ~
按照 C++ 的要求,只要有
new
就要有相應的 delete
。這個 new
是在建構函式裡 new
的,就是出生的時候。所以在死掉的時候,就是呼叫解構函式時,我們必須對指標進行 delete
操作。我們來在main()
函式中建立一個 NoName
例項物件來 測試一下:
int main(){
NoName a;
return 0;
}
- 1
- 2
- 3
- 4
- 5
並且在 main()
函式的 }
這一行新增一個斷點,如圖所示:
執行輸出:
建構函式被呼叫了!
解構函式被呼叫了!
- 1
- 2
在定義 a
物件的時候,呼叫了 NoName
類的建構函式,在main()
函式執行完的時候,就是執行完 return 0;
這句話後,main()
函式的執行域結束,所以就要殺掉 a
物件,所以這個時候會呼叫 NoName
類的解構函式。
那麼,如果我們在 main()
函式中使用 new
關鍵字來建立一個 NoName
類的例項化物件,會出現什麼樣的執行效果呢? main()
函式中的程式碼如下:
int main(){
NoName a;
NoName *p = new NoName;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
執行輸出:
建構函式被呼叫了!
建構函式被呼叫了!
解構函式被呼叫了!
- 1
- 2
- 3
這裡使用 new
建立的物件,就必須要使用 delete
來釋放它。 牢牢的記住:new
和 delete
是一對。正確的程式碼是下面這個樣子的:
int main(){
NoName a;
NoName *p = new NoName;
delete p;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
執行輸出:
建構函式被呼叫了!
建構函式被呼叫了!
解構函式被呼叫了!
解構函式被呼叫了!
- 1
- 2
- 3
- 4
建構函式 和 解構函式 各有各的用途,在建構函式中,我們來獲取資源;在解構函式中,我們來釋放資源。釋放了之後,這些資源就會被回收,可以被重新利用。
比如說,我們在建構函式裡開啟檔案,在解構函式裡關閉開啟的檔案。這是一個比較好的做法。
在建構函式裡,我們去連線資料庫的連線,在解構函式裡關閉資料庫的連線。
在建構函式裡動態的分配記憶體,那麼在解構函式裡把動態分配的記憶體回收。
建構函式 和 解構函式 之間的操作是向對應的。
如果我們不寫解構函式,C++ 會幫我們寫一個解構函式。C++幫我們寫的這個解構函式只能做一些很簡單的工作,它不會幫助我們去開啟檔案、連線資料庫、分配記憶體這些操作,相應的回收,它也不會給我們寫。所以需要我們自己手動的寫。(如果要做這些操作,我們必須自己寫。)
如果我們自己寫了解構函式,記住三個原則:
如果你寫了解構函式,就必須同時寫賦值建構函式 和 賦值操作符。你不可能只寫一個。
賦值建構函式:
- 1
在賦值的時候,不是講指標賦值過來,而是將指標對應指向的字串賦值過來,這是最關鍵的一步。
因為我們寫了解構函式,就必須要將賦值建構函式寫上:
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "建構函式被呼叫了!" << endl;
}
NoName(const NoName & other);
~NoName();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
NoName::NoName(const NoName & other){
pstring = new std::string;
*pstring = *(other.pstring);
i = other.i;
d = other.d;
}
- 1
- 2
- 3
- 4
- 5
- 6
除了要寫 賦值建構函式,還要寫賦值操作符。
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "建構函式被呼叫了!" << endl;
}
NoName(const NoName & other);
~NoName();
NoName& operator =(const NoName &rhs);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
NoName& NoName::operator=(const NoName &rhs){
pstring = new std::string;
*pstring = *(other.pstring);
i = other.i;
d = other.d;
return *this;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
只要你寫了解構函式,就必須要寫 賦值建構函式 和 賦值運算子,這就是著名的 三法則 (rule of three)
總結:
在設計一個類的時候,如果我們一個建構函式都沒有寫,那麼 C++ 會幫我們寫一個建構函式。只要我們寫了一個建構函式,那麼 C++ 就不會再幫我們寫構造函數了。
建構函式可以過載,可以寫很多個,解構函式不能過載,只能寫一個。如果我們沒有寫解構函式,C++會自動幫我們寫一個解構函式。那麼在工作的時候,我們寫的解構函式會被呼叫,呼叫完成之後,C++會執行它自動生成的解構函式。
如果我們寫的類是一個沒有那麼複雜的類,我們可以不需要寫解構函式。如果一個類只要有這些情況:開啟檔案、動態分配記憶體、連線資料庫。簡單的說:就是隻要建構函式裡面有了 new
這個關鍵詞,我們就需要自己手動編寫解構函式。
那麼如果我們寫了解構函式,就必須要注意三法則:同時編寫:解構函式、賦值建構函式、賦值運算子。
完整的程式碼:
#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
NoName():pstring(new std::string)
, i(0), d(0){
cout << "建構函式被呼叫了!" << endl;
}
NoName(const NoName & other);
~NoName();
NoName& operator =(const NoName &rhs);
private:
std::string * pstring;
int i;
double d;
};
NoName::~NoName(){
cout << "解構函式被呼叫了!" << endl;
}
NoName::NoName(const NoName & other){
pstring = new std::string;
*pstring = *(other.pstring);
i = other.i;
d = other.d;
}
NoName& NoName::operator=(const NoName &rhs){
pstring = new std::string;
*pstring = *(rhs.pstring);
i = rhs.i;
d = rhs.d;
return *this;
}
int main(){
NoName a;
NoName *p = new NoName;
delete p;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47