《Effective C++》 筆記:Tips05-Tips08

Tips05:瞭解C++悄悄編寫並調用哪些函數

編譯器自動生成的函數包括數據庫



  1. 1. 構造函數(default)小程序

  2. 2. 拷貝構造函數(copy)數組

  3. 3. 析構函數ide

  4. 4. 拷貝複製運算符(copy assignment)函數



若是一個class的成員變量含有reference(引用)類型,那麼必須本身定義copy assignment操做符。spa



編譯器自動構造的copy assignment的默認操做,是逐一調用成員變量的copy assignment,可是reference沒有copy assignment,也不容許改變。所以,若是不本身定義copy assignment,編譯器就會報錯。3d



reference同樣一旦賦值就不可更改的const類型也同樣,只要class的成員變量含有referenceconst類型,就必須自定義copy assignment操做符。指針





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

編譯器自動生成的函數包括orm



  1. 1. 構造函數(default)xml

  2. 2. 拷貝構造函數(copy)

  3. 3. 析構函數

  4. 4. 拷貝複製運算符(copy assignment)



若是不想讓編譯器,自動生成上述函數,而且禁止調用這些函數,該將該函數聲明爲private,而且沒必要實現。



一般,禁止的函數爲2.拷貝構造函數和4.拷貝賦值運算符




Tips07:爲多態基類聲明virtual析構函數

這裏給多態基類特殊標記,由於不是全部的基類都須要virtual析構函數的,正由於用到了多態,纔有了virtual析構函數的必要。





1、爲何要爲多態基類聲明virtual析構函數。



先來看一段簡單的小程序



class TimeKeeper{           
public:          
       TimeKeeper(void){}           
       ~TimeKeeper(void){           
              std::cout << "delete TimeKeeper" << std::endl;           
       }           
};

   

class AtomicClock : public TimeKeeper{
public:
       ~AtomicClock(void){
              std::cout << "delete AtomicClock" << std::endl;
       }
};



客戶端代碼:    

TimeKeeper *timer = new AtomicClock();
delete timer;



p_w_picpath003

能夠發現,程序並無調用子類AtomicClock的析構函數,這就致使AtmoicClock對象並無被銷燬,而其父類TimeKeeper對象卻被正常銷燬,形成一種「局部銷燬」,造成所謂的內存泄露。



如今爲TimerKeeper的析構函數加上關鍵字「virtual

class TimeKeeper{       
public:      
       TimeKeeper(void){}       
       virtual ~TimeKeeper(void){       
              std::cout << "delete TimeKeeper" << std::endl;       
       }       
};

 





p_w_picpath006

能夠看到,子類AtomicClock的析構函數也被正常調用,這樣纔是完成的對象銷燬。





事實上,任何帶有virtual方法的class,都須要virtual析構函數。很簡單,既然class帶有virtual方法,其目的就是想讓其子類重載該virtual方法,也就說明該class是多態基類,既然是多態基類,只有帶有virtual析構函數,纔不會出現「局部銷燬」。



2、不要繼承沒有virtual析構函數class,尤爲是標準庫的類。



3、若是一個class,不被任何類繼承,不要將其析構函數設爲virtual      



有時,爲了方便,無論該類是否是基類(base class),都將其析構函數設爲virtual。可是,設爲virtual結構是有代價的。

class Point{
public:
       Point(int x, int y);
       ~Point();
private:
       int x, y;
};

若是int32bits,那一個Point對象就是64bits。若是Point的析構函數virtual,那麼Point的大小就不止64bits

對象須要攜帶額外的信息,來決定運行期間到底運行那個virtual函數,而決定權是在一個叫vptr(虛函數表指針)的手上,vptr指向一個由函數指針構成的數組——vtbl(虛函數表)。每一個帶有virtual函數的class都有一個vtbl,例如virtual析構函數Point。當對象調用某一virtual函數時,實際被調用的是vptr指向的那個vtbl

所以,在32位機上,2*32bits(int)+32bits(vptr) = 96bits。不管何種類型的指針,大小均爲32bits

64位機上,2*64bits(int)+64bits(vptr) = 128bits。同理在64位機上,指針爲64bits

這就致使C++對象再也不和其餘語言(C)有着相同的結構,也就再也不可能把他傳遞(或接受)其餘語言縮寫的函數。

總的來講,就是喪失的移植性

PS:其實我一直不太理解,書上這段話的含義,virtual原本就是C++的關鍵字,既然使用它,還談什麼和傳遞給CC原本就不認識virtual






4、若是class沒有任何一個純虛函數,卻又想讓該classabstract class

想讓class成爲abstract class(抽象類)的緣由是,不想讓該class被實例化,既不能new。但該類的全部普通方法均要實現,這又不構成抽象類的條件。



方法就是,將析構函數設置爲純虛函數(pure virtual)


class AWOV{
public:
       AWOV(void);
       virtual ~AWOV(void) = 0;
};
                     
class Child : public AWOV{
public:
       Child(void);
       ~Child(void);
};



客戶端代碼:


AWOV *a = new Child();
delete a;

若是僅僅是這樣,運行程序,你會發現,程序報錯



p_w_picpath009

這裏就涉及到析構函數的運行方式,深層派生(most derived)class的析構函數最早被調用,而後是每個base class的析構函數被調用。在本例中,當ddelete a時,Child的析構函數被調用,而後是AWOV的析構函數,可是AWOV純虛析構函數並無實現,所以形成連接錯誤。



所以,就算析構函數被定義爲pure virtual,它也必定要有實現!從資源釋放的角度來看,析構函數是釋放資源和內存的關鍵,所以必需要實現它。



PS:爲何普通的方法聲明爲pure virtual後,就不要實現了呢?



  1. 1. 不須要實現



  1. 2. 實現也沒用



普通的方法聲明爲pure virtual,就是但願有子類區重載該方法,若是子類重載該pure virtual方法,那子類也是抽象類,沒法被實例化。所以,必須有子類重載該pure virtual方法,一旦該pure virtual方法被重載,那它的實現根本不會被調用!


PS:構造函數必定不能是virtual,由於用到虛函數,就要使用虛函數表(Virtual Table)。但此時,構造函數並沒執行,當前對象還沒構造出來,所以虛函數表更是不存在。


Tips08:別讓異常逃離析構函數

這條Tips的含義就是,不要在析構函數中拋出異常。釋放對象的過程會調用析構函數(這點沒什麼好說的),若是連續釋放多個對象,而又有不止一個對象的析構函數拋出異常,就會致使程序中出現多個異常。事實上,在兩個異常的存在下,C++程序不是結束執行就是產生不明確結果。



1、對會拋出異常的操做,class應提供普通的方法,這樣客戶纔能有機會處理異常。



對於某些操做,當程序執行時,必須調用這些操做。例如,關閉數據庫的鏈接。通常爲了防止客戶忘了關閉鏈接,會將關閉操做放在析構函數中。根據Tips的含義,若是發生異常,析構函數只能使程序關閉,或者吞掉該異常,讓程序繼續執行。但這兩種方法,都會使客戶沒法對異常進行處理/響應



所以,惟一可行的辦法,就是讓客戶本身處理異常,一旦程序由於異常而不能正常運行,客戶就應該知道本身處理異常。因此,class應該爲會拋出異常的操做提供方法。





析構函數對於會拋出異常操做的處理方式。



有兩種處理方式



  遇到異常就結束程序,用過abort完成。

try{
}catch (...){       
       std::abort();       
}


  遇到異常就吞下

try{
}catch (...){       
             製做運轉記錄,記下對close的調用失敗
}
相關文章
相關標籤/搜索