1. 程式人生 > >c++解構函式三分法則

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