C++解構函式詳解

2020-07-16 10:04:22
解構函式(destructor)是成員函數的一種,它的名字與類名相同,但前面要加~,沒有引數和返回值。

一個類有且僅有一個解構函式。如果定義類時沒寫解構函式,則編譯器生成預設解構函式。如果定義了解構函式,則編譯器不生成預設解構函式。

解構函式在物件消亡時即自動被呼叫。可以定義解構函式在物件消亡前做善後工作。例如,物件如果在生存期間用 new 運算子動態分配了記憶體,則在各處寫 delete 語句以確保程式的每條執行路徑都能釋放這片記憶體是比較麻煩的事情。有了解構函式,只要在解構函式中呼叫 delete 語句,就能確保物件執行中用 new 運算子分配的空間在物件消亡時被釋放。例如下面的程式:
class String{
private:
    char* p;
public:
    String(int n);
    ~String();
};
String::~String(){
    delete[] p;
}
String::String(int n){
    p = new char[n];
}
String 類的成員變數 p 指向動態分配的一片儲存空間,用於存放字串。動態記憶體分配在建構函式中進行,而空間的釋放在解構函式 ~String() 中進行。這樣,在其他地方就不用考慮釋放空間的事情了。

只要物件消亡,就會引發解構函式的呼叫。下面的程式說明了解構函式起作用的一些情況。
#include<iostream>
using namespace std;
class CDemo {
public:
    ~CDemo() {  //解構函式
        cout << "Destructor called"<<endl;
    }
};

int main() {
    CDemo array[2];  //建構函式呼叫2次
    CDemo* pTest = new CDemo;  //建構函式呼叫
    delete pTest;  //解構函式呼叫
    cout << "-----------------------" << endl;
    pTest = new CDemo[2];  //建構函式呼叫2次
    delete[] pTest;  //解構函式呼叫2次
    cout << "Main ends." << endl;
    return 0;
}
程式的輸出結果是:
Destructor called
-----------------------
Destructor called
Destructor called
Main ends.
Destructor called
Destructor called

第一次解構函式呼叫發生在第 13 行,delete 語句使得第 12 行動態分配的 CDemo 物件消亡。

接下來的兩次解構函式呼叫發生在第 16 行,delete 語句釋放了第 15 行動態分配的陣列,那個陣列中有兩個 CDemo 物件消亡。最後兩次解構函式呼叫發生在 main 函數結束時,因第 11 行的區域性陣列變數 array 中的兩個元素消亡而引發。

函數的引數物件以及作為函數返回值的物件,在消亡時也會引發解構函式呼叫。例如:
#include <iostream>
using namespace std;
class CDemo {
public:
    ~CDemo() { cout << "destructor" << endl; }
};

void Func(CDemo obj) {
    cout << "func" << endl;
}
CDemo d1;
CDemo Test() {
    cout << "test" << endl;
    return d1;
}

int main() {
    CDemo d2;
    Func(d2);
    Test();
    cout << "after test" << endl;
    return 0;
}
程式的輸出結果是:
func
destructor
test
destructor
after test
destructor
destructor

程式共輸出 destructor 四次:
  • 第一次是由於 Func 函數結束時,引數物件 obj 消亡導致的。
  • 第二次是因為:第 20 行呼叫 Test 函數,Test 函數的返回值是一個臨時物件,該臨時物件在函數呼叫所在的語句結束時就消亡了,因此引發解構函式呼叫。
  • 第三次是 main 函數結束時 d2 消亡導致的。
  • 第四次是整個程式結束時全域性物件 d1 消亡導致的。