Effective C++: 02構造、析構、賦值運算

來源:互聯網
上載者:User

標籤:好的   運算   prevent   classes   結合   dynamic   cte   time   客戶   

05:瞭解C++默默編寫並調用哪些函數

         1:一個空類,如果你自己沒聲明,編譯器就會為它聲明(編譯器版本的)一個copy建構函式、一個copy assignment操作符和一個解構函式。此外如果你沒有聲明任何建構函式,編譯器也會為你聲明一個default建構函式。所有這些函數都是public且inline的。

         2:只有當這些函數被調用時,它們才會被編譯器建立出來。

         3:編譯器建立的解構函式是個non-virtual,除非這個class的base class自身聲明有virtual解構函式。

         4:以下情況下,編譯器會拒絕為class生出operator=:

a、類中具有引用成員或者const 常量成員:

template<class T>class NamedObject {public:  NamedObject(std::string& name, const T& value);  ...                               private:  std::string& nameValue;           // this is a reference  const T objectValue;              // this is const};

 

引用和常量必須在定義時進行初始化,不支援賦值操作。因此編譯器拒絕為這樣的類建立operator=函數;

b、如果某個base class將copy assignment操作符聲明為private,編譯器也拒絕為其derived class產生一個copy assignment操作符。

 

06:若不想使用編譯器自動產生的函數,就應該明確拒絕

         1:如果不希望class支援複製初始化或者賦值操作,因為不定義copy建構函式和copy assignment操作符,編譯器會自動建立一個,因此不定義這倆函數達不到這個目的。

         2:因為編譯器建立的函數都是public的,為了阻止這些函數被建立出來,可以將copy建構函式或copy assignment操作符聲明為private。這樣便阻止了編譯器建立這些函數,而且類的使用者也無法調用它們。

         3:上面的做法還是有漏洞,因為類的成員函數和友元函數還是可以調用你的private函數。這種情況下,可以僅僅聲明而不去定義它們。這種情況下,如果有成員函數或友元函數調用它們的話,將會產生一個串連錯誤。

         因此,將複製建構函式和賦值操作符聲明為private且不去定義它們,當類的使用者企圖拷貝時,編譯器會阻止他;如果在成員函數或友元函數中這麼做,連接器會發出抱怨。

         4:將串連期錯誤移至編譯期是可能的(而且那時好事,畢竟越早偵測出錯誤越好),只要定義一個Uncopyable類,並將自己的類繼承該類就好:

class Uncopyable {protected:                                   // allow construction  Uncopyable() {}                            // and destruction of  ~Uncopyable() {}                           // derived objects...private:  Uncopyable(const Uncopyable&);             // ...but prevent copying  Uncopyable& operator=(const Uncopyable&);};

 

         任何人(包括成員函數或友元函數)嘗試拷貝Uncopyable類的衍生類別對象時,編譯期便試著產生一個copy建構函式和一個copy assignment操作符。這些函數的編譯器產生版會嘗試調用其base class的對應函數,那些調用會被編譯器拒絕,因為其base class的拷貝函數是private。

 

07:為多態基類聲明virtual解構函式

         1:當derived class對象經由一個base class指標刪除,而該base class帶著一個non-virtual解構函式,則其結果是未定義的。實際執行時通常發生的是對象的derived成分沒被銷毀,而其base class成分通常會被銷毀,於是造成一個詭異的“局部銷毀”對象。

         2:任何class只要帶有virtual函數都幾乎確定應該也有一個virtual解構函式。

         3:如果class不含virtual函數,通常表示它並不願意被用做一個base class。當class不企圖被當作base class時,令其解構函式為virtual往往是個饅主意。

欲實現出virtual函數,對象必須攜帶某些資訊,主要用來在在運行期決定哪一個virtual函數該被調用。這份資訊通常是由一個所謂vptr ( virtual table pointer)指標指出。vptr指向一個由函數指標構成的數組,稱為vtbl ( virtual table );每一個帶有virtual函數的class都有一個相應的vtbl。當對象調用某一virtual函數,實際被調用的函數取決於該對象的vptr所指的那個vtb----編譯器在其中尋找適當的函數指標。

因此,無端的將某個class的解構函式聲明為virtual,會增加對象的體積(vptr)。因此許多人的心得是:只有當class內含至少一個virtual函數,才為它聲明virtual解構函式。

 

08:別讓異常逃離解構函式

         C++並不禁止解構函式吐出異常,但它不鼓勵這麼做。如果某個類的解構函式有可能拋出異常,則要麼:拋出異常時直接調用abort退出程式;要麼拋出異常時吞下異常,僅記錄日誌。

 

09:絕不在構造和析構過程中調用virtual函數

         1:不要再建構函式和解構函式中,調用virtual函數。

2:在base class構造期間,如果建構函式中調用了virtual函數,即使當前正在構造derived class(構造衍生類別對象時,需要首先構造其基類部分),virtual函數也是base class中的版本。也就是說;在base class構造期間,virtual函數不是virtual函數。

在derived class對象的base class構造期間,對象的類型是base class而不是derived class。不只virtual函數會被編譯器解析至(resolve to)base class,若使用運行期類型資訊(runtime type information,例如dynamic_cast和typeid),也會把對象視為base class類型。

3:相同道理也適用於解構函式。一旦derived class解構函式開始執行,對象內的derived class成員變數便呈現未定義值,所以C++視它們彷彿不再存在。進入base class解構函式後對象就成為一個base class對象。

4:確定你的建構函式和解構函式都沒有調用virtual函數,而它們調用的所有函數也都要服從這一約束。

 

10:令operator=返回一個reference to *this

         1:賦值時,可以將其寫成連鎖形式:x = y = z = 15;賦值採用右結合律,因此這個運算式等價於:x = ( y = ( z = 15 ) );

         2:為了實現“連鎖賦值”,賦值操作符必須返回一個reference指向操作符的左側實參。這是你為classes實現賦值操作符時應該遵循的協議:

Widget& operator=(const Widget& rhs)   // return type is a reference to{                                      // the current class  ...  return *this;                        // return the left-hand object}

 

 

         3:這個協議不僅適用於標準的賦值形式,也適用於所有賦值相關運算,比如+=。

 

11:在operator=中處理“自我賦值”

         1:“自我賦值”發生在對象被賦值給自己時,不要認定客戶絕不會那麼做,而且自我賦值並不總是那麼可被一眼辨識出來,例如:a[i] = a[j];這條語句中,如果i和j相同,這便是自我賦值;再比如:*px = *py;如果px和py恰巧指向相同,這也是自我賦值。

         2:“自我賦值”時,可能會掉進“在停止使用資源之前意外釋放了它”的陷阱。比如:

Widget& Widget::operator=(const Widget& rhs){  delete pb;      pb = new Bitmap(*rhs.pb);   return *this;  }

 

這裡的自我賦值問題是,operator=函數內的*this和rhs有可能是同一個對象。果真如此delete就不只是銷毀當前對象的bitmap,它也銷毀rhs的bitmap。

欲阻止這種錯誤,傳統做法是藉由operator=最前面的一個“證同測試(identity test )”達到“自我賦值”的檢驗目的:

Widget& Widget::operator=(const Widget& rhs){  if (this == &rhs) return *this;   // identity test: if a self-assignment,                                    // do nothing  delete pb;  pb = new Bitmap(*rhs.pb);  return *this;}

 

 

3:這個新版本仍然存在異常方面的麻煩。更明確地說,如果”new Bitmap”導致異常(不論是因為分配時記憶體不足或因為Bitmap的copy建構函式拋出異常),Widget最終會持有一個指標指向一塊被刪除的Bitmap。

令人高興的是,讓operator=具備“異常安全性”往往自動獲得“自我賦值安全”的回報。因此愈來愈多人對“自我賦值”的處理態度是傾向不去管它,把焦點放在實現“異常安全性”上。例如,我們只需注意在複製pb所指東西之前別刪除pb:

Widget& Widget::operator=(const Widget& rhs){  Bitmap *pOrig = pb;               // remember original pb  pb = new Bitmap(*rhs.pb);       // make pb point to a copy of *pb  delete pOrig;                     // delete the original pb  return *this;}

 

現在,如果”new Bitmap”拋出異常,pb保持原狀。即使沒有證同測試,這段代碼還是能夠處理自我賦值,因為我們對原bitmap做了一份複件、刪除原bitmap、然後指向新製造的那個複件。它或許不是處理“自我賦值”的最高效辦法,但它行得通。

 

         4:在operator=函數內確保代碼不但“異常安全”而且“自我賦值安全”的一個替代方案是,使用所謂的copy and swap技術。

它是一個常見而夠好的operator=撰寫辦法:

Widget& Widget::operator=(const Widget& rhs){  Widget temp(rhs);             // make a copy of rhs‘s data  swap(temp);                   // swap *this‘s data with the copy‘s  return *this;}

 

或者,可能更常見的是下面這種寫法:

Widget& Widget::operator=(Widget rhs){  swap(rhs);  return *this;}

 

 

12:複製對象時勿忘其每一個成分

1:如果自己寫複製建構函式或賦值操作符而不使用編譯器的版本,則需要注意的是:如果你為class添加一個成員變數,你必須同時修改複製建構函式和賦值操作符函數(你也需要修改class的所有建構函式,以及任何非標準形式的operator=(比如+=))。如果你忘記,編譯器不太可能提醒你。

2:任何時候只要你承擔起“為derived class撰寫copying函數”的重責大任,必須很小心地也複製其base class成分。那些成分往往是private,所以你應該讓derived class的copying函數調用相應的base class函數。

Effective C++: 02構造、析構、賦值運算

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.