C++必知系列(一)——構造/析構/賦值

來源:互聯網
上載者:User

一. 編譯器何時為類產生合適的特殊預設函數

      當聲明如下一個空類時:

                        class CA {};

一般認為C++編譯會在背後默默幫你產生5個函數:預設建構函式,拷貝建構函式,解構函式,賦值運算子多載函數,取地址運算子多載函數,結果類被擴充為如下形式:

                       class CA()

                       {

                            public:

                                  CA() {...}                                                                   

                                  CA(const CA& other) {...}                                          

                                  ~CA() {...}                                                                 

                                 CA& operator=(const CA& other) {...}                     

                                 CA* operator&() {...}                                               

                       }

但實際情況並非如此,編譯器只有認為需要的時候才產生相應函數,這體現了C++的效率至上理念,對於如上空類根本就不需要產生任何函數,因為這些預設函數沒有任何有意義的事可做,當你在類裡顯式聲明了任何類型的建構函式時(包括拷貝建構函式),編譯器便不會為你產生預設建構函式,同樣其他四個函數當在類裡有顯式聲明時,編譯器都不會產生預設的,這裡要特彆強調一下operator&()函數,可能很多人不知道它的存在,如果你不去顯式申明,確實沒有存在的必要,就是擷取對象的地址,但你在類裡顯式定義後,也許會改變它的預設意義,可能會造成使用者的困惑, 例如:

                        class CA { CA* operator&() {...}};

                        CA a;

                        CA* p = &a;   //這裡&a等價與a.operator&()調用,其意義完全取決於設計者的實現,而未必就是取對象a的地址。

那麼除去operator&(),在沒有顯式申明其他函數的前提下,編譯器何時會為你產生這些特殊函數呢,以下列舉幾種常見情況(更多資訊可參考《深度探索C++物件模型》):

1. 類裡聲明了虛函數

2. 基類體系某一個裡申明了虛函數

3. 基類體系裡某一個裡顯式定義了相應函數          

4. 含有類成員,類成員出現了以上情況之一                          

二. 拷貝構造/賦值操作注意事項

    當一個類裡申明了一個引用成員或const成員,必須顯式定義operator=(...)才能處理對象賦值問題,否則編譯無法通過,如下所示:

                     class CB 

                     {

                       public:

                          CB() : a(1) , b(a) {}

                          int a;

                          int& b;

                     };

                      CB b , a;

                     a = b; //無法通過編譯

當一個衍生類別顯式定義了拷貝建構函式或operator=(...),他們並不會預設的去調基類的相應函數,這可能與你的預期不一致,導致對象拷貝賦值的不完全,如下所示:

                   class Base

                   {

                    public:

                        Base() {...}

                        Base(const Base& other) : a(other.a) {}

                        Base& operator=(const Base& other) { a = other.a; }

                        int a;

                   };

                   class Derive : public Base

                   {

                     public:

                          Derive() {...}

                          Derive(const Derive& other) : b(other.b) {...}                         //不是調用Base的拷貝建構函式,而是調用無參建構函式Base(),導致Base部分沒有拷貝

                          Derive& operator=(const Derive& other) { b = other.b; }      //沒有調用基類operator=(...)函數

                          int b;

                   }

要想實現完全拷貝,需要自己顯式申明:

                   class Derive : public Base

                   {

                     public:

                          Derive() {...}

                          Derive(const Derive& other) : Base(other), b(other.b) {...}                     

                          Derive& operator=((const Derive& other) { Base::operator=(other); b = other.b; }            

                          int b;

                   }

                   

三. 虛擬解構函式

     在C++裡,建構函式是沒辦法被顯式調用的,只能由編譯器調用,但解構函式是可以被顯式調用的。一個類是否都應該有一個顯式的虛擬解構函式呢?不一定,只有當該類需要當作基類,而且需要delete一個該基類指標,而該基類指標實際指向其衍生類別時,才需要,目的是防止記憶體流失,當不存在如此情況時,若不需要在解構函式裡做什麼工作,可完全不聲明,由編譯器去決定是否需要產生一個預設的解構函式。解構函式有時會聲明為純虛的,這一般出現在定義一個不能執行個體化的類,但該類除了解構函式沒有其他的虛函數,此時可將解構函式聲明為純虛的。

四. 構造/解構函式裡原則上不應該有的操作

     在構造/解構函式裡一般不要調用虛函數,其行為一般非你所預期,這依賴於實現,目前的實現來講,不會發生多態行為,而是像調用一個普通成員函數一樣直接產生編譯期綁定,如下例:       

           class Base
          {
           public:
               Base() { test(); }
               virtual void test() {cout << "Base::test" << endl; }
          };
          class Derive : public Base
         {
          public:
            Derive() { test(); }
             virtual void test() { cout << "Derive::test" << endl; }
         };

  Derive d;

 輸出:Base::test

           Derive::test

另不要在構造/析構完成過複雜容易引起異常的操作。 

五. 限制對象建立與複製

     當想讓一個對象只能在棧上分配,而禁止在堆上分配時,可以通過將opertor new(size_t size)申明為類的私人函數方式實現,反過來可通過申明類的建構函式為私人的,同時定義一個靜態工廠函數,實現裡通過new的方式返回對象指標即可,這種方式也能控制對象建立的數量,當然還有其他的方法,對對象建立的控制無論是建立方式還是數量都基本可通過顯式定義operator new(...)/operator delete(...), 控制構造/解構函式的存取權限,另外可能輔助一些靜態工廠函數來實現。當想禁止一個對象的複製時,可通過將拷貝建構函式及operator=(...)顯式申明為私人的。

六. 成員初始化列表機制

     一個類裡成員的初始化有兩種方式,常見的是在建構函式裡初始化,但這並非真正的初始化,因為成員在進入建構函式體之前已完成了預設的初始化工作,在建構函式內都只能算賦值動作,如果想真正顯式執行特定的類成員初始化動作,可採用第二種初始化列表機制,如下類:

class CA

{

 public:

    CA() {...}

    CA(int a) {...}

};

class CB

{

public:

   CB(int a) : ca(a) {...}   //通過初始化列表機制顯式指定相應ca的構造方法  

   CA ca;

 需要注意的是初始化是按照成員在類裡申明順序初始化的,這有時可能會導致一些隱晦的錯誤,例如如下定義:

             class CA

             {

              public:

                  CA(): j(1),i(j) {}

             private:

                 int i;

                 int j;

             };

這裡雖然j的初始化放在i之前,但由於聲明時i在j之前,所以i會先初始化,而此時j處於不確定狀態。

 另外需要注意的是有些類成員只能通過成員初始化列表機制初始化,有如下幾種常見情況:

1. 類成員沒有預設建構函式或可不帶參數調用的建構函式(註:這裡沒有說無參建構函式是考慮到預設參數的存在);

2. 類成員為參考型別;

3. 類成員為const修飾型;

七. 類對象的構造過程

     為了下面敘述方便,先提出一個擴充建構函式的概念,由編譯器在你編寫的實際建構函式前插入必要代碼構成,當你定義一個類對象時,實際上初始化是通過調用擴充建構函式完成,在進入你所編寫的建構函式之前,這段必要代碼會完成基類擴充建構函式,類類型成員擴充建構函式調用,虛表指標初始化等關鍵工作,那麼一個最具普遍意義的類對象的構造過程如下:

1. 調用類的擴充建構函式,當有繼承存在時進行步驟2,否則到步驟3;

2. 當不存在虛擬繼承時,調用基類的擴充建構函式,當存在多個基類時,按照申明順序依次調用,基類的構造行為同樣,這樣就保證了按照繼承樹從跟往下構造的順序;

   存在虛擬繼承的情況比較複雜一點,平時很少用到,這裡不細說了,具體細節可參看《深度探索C++物件模型》,這裡指出一點技巧,可以利用虛擬繼承的相關特性設計一個不能被繼承使用的類。

3. 當存在自訂類型成員時,按照其在類裡的申明順序依次調用相應的擴充建構函式;

4. 進入你編寫的實際建構函式完成整個構造過程;

下面舉例說明:

class Base1

{

public:

   Base1() { cout << "Base1 Constructor..." << endl; }

};

class Base2

{

public:

  Base2() { cout << "Base2 Constructor..." << endl; }

};

class Member

{

public:

  Member() { cout << "Member Constructor..." << endl; }

};

class Derive : public Base1 , pubic Base2

{

public:

    Derive() { cout << "Derive Construcotor..." << endl; }

    Member member;

};

Derive d;

輸出:

Base1 Constructor...

Base2 Constructor...

Member Constructor...

Derive Constructor...

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.