1:為多態基類聲明virtual析構函數
? ? ? ?當我們創建一個base class指針指向新生成的derived class時,當刪除基類指針時,如果base class是一個non-virtual析構函數時,實際執行的結果通常是derived class中的base成分被銷毀,derived成分沒有被銷毀;
? ? ? 通常解決這種問題的方法是把base class中的析構函數聲明為virtual;
? ? ? 并不是只有析構函數可以聲明為virtual,其他的函數也可以,virtual函數的目的是為了讓derived class客制化,也就是實現動態聯編;
? ? ?任何class中只要帶有virtual函數都需要提供一個virtual析構函數;
? ? 想要實現虛函數,對象必須提供某些信息,來決定運行期決定那個virtual函數被調用,通常這個信息有vptr(virtual table pointer)來表示,vptr指向一個函數指針構成的數組,稱為vtbl(virtual table),每一個帶有virtual函數的class都有一個對應的vtbl,對象調用virtual函數的時候,實際上調用的是vptr指向的vtbl;
? ? ?一般來說,如果不想把base class用于繼承,一般不要定義virtual函數,畢竟vptr指針會占內存的;不要試圖去繼承一個不帶虛析構函數的類,這樣會出問題的;
? ? ?抽象類(不能被實體化的類,不能用這種類來創建對象):類中帶一個pure virtual析構函數,通常作為base classes;由于析構的順序和初始化的順序相反,因此在聲明純虛析構函數時,需要給虛析構函數提供一份定義,這樣編譯器在derived class的析構函數創建一個對base class析構函數的調用動作,即使在base class中未發生析構,代碼如下:
?class AMOV{
public:
? ? ? ? ?virtual ~AMOV() =0;//pure virtual析構函數
};
AMOV::~AMOV() { }
? ? 結論:polymorphic(帶多態性質的)base classes應該聲明一個virtual析構函數,如果class帶有任何的virtual函數,那么應該有一個virtual析構函數;
? ? ? ? ? ? ?classes的設計目的如果不是作為base classes使用,或不是為了多態性就不應該聲明virtual析構函數;
2:別讓異常逃離析構函數
? ? ? ? 在兩個異常同時存在的情況下,程序若不結束執行就會導致不明確的行為;
class dbconnection{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?class dbconn{//管理對吧connection對象
public:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?public:
? ? ? ? ? static dbconnction create();//創建對象? ? ? ? ? ? ? ? ? ? ? ? ? ?~dbconn()? {? ? ? db.close() ;? ?}//關閉數據庫連接
? ? ? ? ? void close();//關閉系統? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?private:? dbconnection db;
};? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?};
?問題:如果close()調用失敗,就會產生異常,而析構函數中沒有處理異常的機制,異常在析構函數向上拋出;
解決方案:在析構函數中建立異常處理機制或者直接吞掉異常(如果在吞掉異常后,程序依舊可以可靠的運行);? ? ? ? ? ? ? ? ? ? ~dbcnn(){
? ? ? ? ? ? ? ?try {db.close();}
? ? ? ? ? ? ? ?catch(...){//捕獲異常
? ? ? ? ? ? ? ? ? ? ? ? ? ?std::abort() ;//終止程序? ? ? ? ? ? ? //方案1需要這一句? ? ? ? //方案2不需要
? ? ? ? ? ? ? ? ? ? ? ? ? }
}
另外一種比較好的方法就是:重新設計dbconn接口,設置一個普通函數讓用戶判斷是否會產生異常,
class dbcnn{
public:
?void close(){
? ? ? ? ? ? ?db.close();
? ? ? ? ? ? ?closed=true;
? ? ? ? ? ? ?}?
~dbcnn() {
if(!closed){
? ? ? ? try{
? ? ? ? ? ? ? ? db.close();
? ? ? ? ?}
? ? ? ? ?catch(...){
? ? ? ? }
? ? }
? }
private:
? ? ? ? ? ? dbconnection db;
? ? ? ? ? ? bool closed;
};
3:不要在構造和析構函數中調用virtual函數
? ? ? ? ? 對于構造函數而言,不能在構造過程中調用virtual函數的原因:base class在構造過程中,virtual函數不會下降到derived class,因為在base class構造函數執行時derived class的成員還沒有初始化,如果此時virtual函數下降到derived class中,勢必會調用未初始化的變量,而C++不允許這一現象發生,在base class構造期間,virtual函數不再是virtual函數;更本質的原因是在derived class對象的base class構造期間,對象是base class而不是derived class,不只是virtual函數會被編譯器解析為base class,若使用運行期類型信息(如dynamic_cast和typeid),也會把對象視為base class類型,因此對象在derived class構造函數開始執行前不會成為一個derived class對象;
? ? ? ? ?對于析構函數也是同樣的道理(構造和析構的順序相反):一旦derived class析構函數開始執行,對象內的derived class成員變量便呈現出未定義狀態(因為被析構了),進入base class析構函數后對象變成一個base class對象;
? ? ? ? 為了防止傳值過程中出現意外指向未初始化的成員變量,可以充分利用static關鍵字;
4:令operator=返回一個reference to*this
? ? ? 為了使操作符實現連鎖操作,操作符必須返回一個reference指向操作符的左側實參;
? ? ?