標籤:好的 運算 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構造、析構、賦值運算