05.瞭解C++默默編寫並調用哪些函數
C++默默編寫的函數(程序員沒有聲明的前提下):default構造函數、copy構造函數、copy assignment操作符、default析構函數(非virtual)
編譯器產出的函數都是public的
關於構造函數,如果用戶定義了至少一種,就不會有默認構造函數
- 關於copy構造和copy assignment,只有當生出的代碼合法且有適當機會證明它有意義,編譯器纔會自動生出。比如成員變量裏面有引用或者const,無法賦值,編譯器沒法解決,就不生成copying函數
結論:
- 編譯器可以暗自爲class創建default構造函數、copy構造函數、copy assignment操作符和析構函數
06.若不想使用編譯器自動生成的函數,就該明確拒絕
自行聲明和定義
將對應的函數聲明爲private而且故意不實現它們
私有的繼承Uncopyable類
07.爲多態基類聲明virtual析構函數
任何class只要帶有virtual函數都幾乎確定應該也有一個virtual析構函數
當class不企圖被當做base class, 令其析構函數爲virtual是不好的
- 因爲virtual的函數導致class存在虛函數表,增大了存儲空間
換而言之,對於析構函數不是virtual的class,不適合作爲base class,典型代表是string類以及STL裏面的類
有時候讓class包含純虛的析構函數是有意義的,注意需要爲它提供定義
08.別讓異常逃離析構函數
析構函數絕對不要吐出異常。如果一個被析構函數調用的函數可能拋出異常,析構函數應該捕捉任何異常,然後吞下它們(不傳播)或結束程序
析構期間出現錯誤,強迫結束的意義在於阻止異常傳播
一般而言,吞掉異常不好,它壓制了某些動作失敗的重要信息
如果客戶需要對某個操作函數運行期間拋出的異常作出反應,那麼class應該提供一個普通函數(而非在析構函數中)執行該操作
- 可以爲行爲上可能存在異常的類別設置對應的操作類,進一步封裝潛在的錯誤操作,並將相關的處理移至其他地方
09.絕不在構造和析構過程中調用virtual函數
如果在構造函數調用virtual函數
base class構造期間,virtual函數絕不會下降到derived classes階層,而是會調用base class的virtual函數。即, base class構造期間,virtual函數不是virtual函數
其根本理由:base class構造期間,根本還不存在derived class,所以如果使用運行期類型的信息,都會把對象視作base class
如果在析構函數調用virtual函數(道理同構造函數)
- 當深入到base class的時候,derived classes已經被析構完了,如果base class調用virtual函數,只會調用base class的
注意:
這裏的意思是,在構造和析構函數裏調用虛函數,效果等效於非虛函數,沒有意義
通過定義帶參數的函數取代基類在析構或者構造函數時使用的虛函數
10.令operator=返回一個reference to *this
最簡單的例子:連鎖賦值的時候,賦值操作符必須返回一個reference指向操作符的右側實參
不僅是賦值,和賦值相關的操作符,如+=、-=等都應該返回引用
11.在operator=中處理“自我賦值”
當某段代碼操作pointers或references,而他們被用來“指向多個相同類型的對象”,就需要考慮這些對象是否爲同一個
賦值過程中,有可能因爲同一個對象之間的賦值操作導致“在停止使用資源之前意外釋放”的錯誤
解決方法:
傳統做法是在operator=函數的最前面,做一個“證同測試”
copy and swap技術:對右值做複製,將臨時值與對象swap
結論:
確保當對象自我賦值時,operator=有良好的行爲。其中技術包括比較“來源對象”和“目標對象”的地址(證同測試)、精心的語句順序、copy-and-swap技術
確定任何函數如果操作一個以上的對象,而其中多個對象是同一個對象時,其行爲仍然正確
12.複製對象時勿忘其每一個成分
copying函數:copy構造函數 + copy assignment
copying函數應該確保賦值“對象內的所有成員變量”及“所有base class 成分”
不要嘗試以某個copying函數實現另一個copying函數,應該將共同機能放進第三個函數,並由兩個copying函數共同調用