Effective C++之2 構造/析構/賦值運算

 

條款05:瞭解C++默認編寫並調用的函數

a)       C++處理一個類之後,如果編寫者沒有聲明,編譯器就會默認生成一個copy構造函數,一個copy assignment操作符(即等號操作符)和一個析構函數,並且這些函數都是publicinline的屬性。

b)       對象在初始化的同時進行賦值,如student s1 = s2;調用的不是等號操作符,而是拷貝構造函數,形式類似於student s1(s2)

c)        對於copy構造函數和copy assignment操作符,編譯器創建的版本只是簡單的將源對象的每一個non-static成員變量拷貝到目標對象。

d)       只要編寫者聲明瞭一個對應類型的函數,如構造函數,編譯器就不會爲其創建default的版本。

e)       如果類內含有reference成員或者const成員,必須自己定義copy assignment操作符,因爲C++不能讓reference所指向的對象被改動替換,const成員也不能再次賦值。

條款06:若不想使用編譯器生成的default函數,就應當明確拒絕

a)       如果你希望你設計的類不支持拷貝構造功能和等號賦值對象的功能,可以將他們聲明爲private類型,從而阻止編譯器創建默認版本,並阻止使用者的調用(private)

b)       只聲明爲private函數並非足夠安全,因爲member函數和friend函數還是可以對其進行調用,所以一個比較完善的辦法是將其聲明爲private並且不定義它。

c)        可以設計一個父類令其符合以上規範,之類繼承它後自然也就有了對應的限制,因爲拷貝複製子類首先需要拷貝複製父類。

條款07:爲多態基類聲明virtual析構函數

a)       一個派生類對象被一個基類指針所指向,則其在析構時調用析構函數分爲兩種情況:

1.1        第一種是析構函數爲虛函數,則是動態綁定,調用時通過對象類的虛表指針來調用,其會先調用子類的析構函數再調用父類的析構函數;

1.2        第二種情況是析構函數爲non-virtual函數,則通過對象名加上函數名的方式調用,即base::funcname()的方式,則直接調用父類的析構函數,造成子類的資源泄漏。

b)       爲了實現virtual函數,對象必須攜帶一個vptr(virtual table pointer)指針來指向虛函數表vtbl(virtual table),即一個函數指針數組。

c)        每個帶有virtual函數的類都有一個隊形的虛表vtbl,當對象通過指針調用的虛函數時,就通過虛表指針來查找對應的虛函數進行調用,實現多態。

d)       虛表指針包含在對象體所在的內存中,所以如果有虛函數對象的體積將會增加4個字節或8個字節(64位系統),此時一個對象就不能再裝入64-bit寄存器(體積過大),所以將所有函數全都聲明爲virtual而不考慮其使用環境是不正確的。

e)       如果類有任何virtual函數,那它也應該擁有一個虛析構函數;但如果類不是爲了作爲基類被繼承而設計,也不是爲了擁有多態性,就不應該聲明虛析構函數。

條款08:別讓異常逃離析構函數

a)       在析構函數中如果有異常拋出就會導致內存泄露,特別是當要析構一個對象數組時。

b)       如果一個析構函數調用的函數可能拋出異常,那麼析構函數應該捕獲該異常併吞下它(不傳播)或結束程序。

c)        如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數而非析構函數執行該操作。

d)       析構函數調用該函數進行異常處理,就提供給了客戶一個處理異常的機會(普通函數中處理)。

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

a)       在一個派生類對象生成的構造中,總是先調用基類的構造函數生成基類所擁有的部分再調用派生類的構造函數生成派生類獨有的部分。

b)       同理,在一個派生類的析構過程中,總是屬於派生類獨有的部分先進行析構,然後基類析構函數析構基類獨有的部分。

c)        在基類的構造函數和析構函數調用期間,派生類對象獨有的部分都是不存在的,所以其只能操作基類獨有的部分,在base class構造期間virtual函數絕對不會下降到derived classes階層。或者說,在base class構造期間,virtual函數不是virtual函數。

d)       如果在base class構造函數中調用了virtual函數,因爲調用base class構造時派生類對象尚未生成,而virtual函數下降至派生類版本,必然調用其local成員,但這些成員根本沒有初始化,這將是一張通往徹夜調試大會的車票。

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

a)       返回一個refere to *this可以實現對於該對象的再次操作和嵌套賦值等,這只是個協議,0並無強制性。

例如,若聲明爲如下形式

Obj& operator=(const Obj& rhs)

{

         ···············

         return *this;

}

 

則可以實現

Obj x,y,z;

x=y=z=············;

(x=y)=z;

連鎖賦值和多次賦值,賦值操作符是右結合的,而等號操作符函數形參傳遞常引用的原因可以見條款20

條款11:在operator=中處理自我賦值

a)       對象的自我賦值可能導致兩個指針指向同一內存,則析構時將導致後一個對象析構的內存已經不存在;即使賦值時是爲每個對象的指針單獨分配一塊內存,那也需要先對左操作數對象的指針內容進行釋放再賦值,但釋放的時候會把右操作數指向的內存一起釋放掉(指向同一對象),導致賦值失敗。 

b)       爲阻止這種錯誤,傳統的做法是在函數最前面做證同測試,達到自我賦值檢驗的目的;亦可利用copy and swap的方法來進行,即通過copy生成一個臨時對象,將等號右操作數拷貝給他,然後左操作數與其進行值交換,最終釋放掉臨時對象。

條款12:複製對象是勿忘其每一個成分

a)       當自行聲明拷貝構造函數時,編譯器提供的缺省拷貝構造函數將不會提供,而自定義拷貝構造函數如果有拷貝成員不完全等錯誤也得不到提醒。如在複製派生類對象時忘記對其對應部分的基類對象進行復制,導致基類對象只有調用默認構造函數生成。

b)       Copying函數應該確保複製了對象內部的所有成員變量所有base class“成分。

c)        不要嘗試用一個copying函數實現另一個copying函數,應該將共同機能放進到第三個函數中,並由兩個copying函數共同調用。

 

發佈了61 篇原創文章 · 獲贊 20 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章