2015年7月3日 星期五

Constructor & Destructor



constructor(建構函式) 與 destructor(解構函式) 是 class 中的兩種特別函式,當主程式中產生某class的物件時,該 class 的建構函式即會自動執行;而當物件生命周期結束,則在物件消滅前會自動執行解構函式。

建構函式和 class 同名,可以有參數,沒有傳回值。解構函式則是 class 名稱前加~符號,沒有參數,也沒有傳回值。由於物件產生時可能會給予不同的參數,所以每一 class 的建構函式可以有很多個,以對應不同的參數。而解構函式因為沒有參數,所以只有一個。

如果程式設計者沒有為 class 撰寫建構函式或解構函式,則編譯器會產生內定的建構函式和解構函式。以下程式可觀察建構函式與解構函式的執行狀況

#include<iostream>
using namespace std;

class myClass
{
public:
   int d[10];
   myClass() {
      cout << "建構函式!" << endl;
   }

   ~myClass() {
      cout << "解構函式!" << endl;
   }
};

void print(myClass x)
{
    for(int i=0; i<10; i++)
        cout << x.d[i] << " ";
    cout << endl;
}

int main()
{
    myClass obj1; //產生物件,應執行 constructor

    print(obj1);

    for(int i=0; i<10; i++)
        obj1.d[i]=i;

    myClass obj2=obj1; //產生物件,應執行 constructor
    print(obj2);
    
    return 0; //obj1 與 obj2 生命週期結束,應執行 destructor
}

程式一開始以 using namespace std; 來表明將使用 std 名稱空間,因 C++ 的標準函式庫都定義在 std 名稱空間,若不宣告使用 namespace std,則當程式使用到 cin cout 這些物件時必須在前加上 std:: 以註明其所在之
名稱空間

這個程式很簡單,主程式先宣告一個 myClass 物件 obj1,接著呼叫 print 函式,把 obj1 裡的陣列印出來。之後再用一個迴圈把數值填入 obj1 裡的陣列,然後再宣告一個 obj2 物件,且把 obj1 的內容做為 obj2 的初始值。最後程式呼叫 print 函式把 obj2 裡的陣列印出來,結束程式。

因為程式中產生了 obj1 obj2 這兩個 myClass 物件,所以 myClass 的建構函式和解構函式都應分別被 invoked 兩次。然而程式執行結果令人驚訝,建構函式只執行一次,而解構函式竟執行了四次!




先前曾提到"由於物件產生時可能會給予不同的參數,所以每一class的建構函式可以有很多個,以對應不同的參數。而解構函式因為沒有參數,所以只有一個",所以對於上面的執行結果,解構函式被執行四次是正確的次數。而建構函式,因 myClass 中只撰寫了一個無參數的建構函式,若 myClass 物件以【有參數】的方式產生時,就會改去執行編譯器自動產生的建構函式,而不是執行 myClass() 這個無參數的建構函式。

myclass obj2=obj1; //以複製方式產生物件,會執行 copy constructor

這就是少了一次"建構函式!"的原因,但多兩次"解構函式!"又是什麼造成的?
為了釐清這個問題,以下把 copy 建構函式添加到 myClass 中,再觀察執行結果

class myClass
{
public:
   int d[10];

   myClass() {
      cout << "建構函式!" << endl;
   }

   myClass(myClass& obj) {
      cout << "COPY Constructor!" << endl;
   }

   ~myClass() {
      cout << "解構函式!" << endl;
   }
};


執行結果似乎更奇怪了,雖然次數有對應,建構函式被執行了四次(其中有三次是執行 copy constructor),而解構函式也被執行了四次。但程式裡明明只有兩個 myClass 物件啊!(又是明明惹的禍)

這是因為 void print(myClass x) 函式的參數型態是 myClass 物件,而且以傳值 (call by value) 的方式傳遞。一個物件的【值】如何產生? C++ 的做法是,自動宣告一個同型態的臨時物件並把主程式的物件參數 copy 過來,再交給函式使用。所以在處理 print(obj1); 時,等於先執行 myClass x=obj1; 然後 print 函式才去印出 x 物件的內容。當 print 完成,返回主程式時,這個臨時物件 x 的生命周期即告結束。

所以每呼叫一次 print 函式一次,就會以 copy 的方式產生臨時的 myClass 物件 x,並在函式返回時消滅,於是 myClass 的 copy constructor 與解構函式都會被執行。 

若要避免這個狀況,可以改用 call by reference 的方式來傳遞物件參數,並加上 const 修飾以避免在函式中更動了物件的內容。

void print(const myClass& x)
{
    for(int i=0; i<10; i++)
        cout << x.d[i] << " ";
    cout << endl;
}

6 則留言:

  1. 終於弄懂destructor了,講得非常清楚!

    回覆刪除
  2. 想請問一這邊的自定義的建構式寫的是pass by reference, 但是print參數傳遞是pass by value , 這個情為什麼這個建構式還會被呼叫 ? 謝謝

    回覆刪除
    回覆
    1. 找到原因了,https://stackoverflow.com/a/11905048/10689760

      刪除
    2. 建構式在物件產生時就會自動執行,跟其他function無關喔!

      刪除