編譯器自動生成的函數包括數據庫
1. 構造函數(default)小程序
2. 拷貝構造函數(copy)數組
3. 析構函數ide
4. 拷貝複製運算符(copy assignment)函數
若是一個class的成員變量含有reference(引用)類型,那麼必須本身定義copy assignment操做符。spa
編譯器自動構造的copy assignment的默認操做,是逐一調用成員變量的copy assignment,可是reference沒有copy assignment,也不容許改變。所以,若是不本身定義copy assignment,編譯器就會報錯。3d
和reference同樣一旦賦值就不可更改的const類型也同樣,只要class的成員變量含有reference和const類型,就必須自定義copy assignment操做符。指針
編譯器自動生成的函數包括orm
1. 構造函數(default)xml
2. 拷貝構造函數(copy)
3. 析構函數
4. 拷貝複製運算符(copy assignment)
若是不想讓編譯器,自動生成上述函數,而且禁止調用這些函數,該將該函數聲明爲private,而且沒必要實現。
一般,禁止的函數爲2.拷貝構造函數和4.拷貝賦值運算符
這裏給多態基類特殊標記,由於不是全部的基類都須要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;
能夠發現,程序並無調用子類AtomicClock的析構函數,這就致使AtmoicClock對象並無被銷燬,而其父類TimeKeeper對象卻被正常銷燬,形成一種「局部銷燬」,造成所謂的內存泄露。
如今爲TimerKeeper的析構函數加上關鍵字「virtual」
class TimeKeeper{ public: TimeKeeper(void){} virtual ~TimeKeeper(void){ std::cout << "delete TimeKeeper" << std::endl; } };
能夠看到,子類AtomicClock的析構函數也被正常調用,這樣纔是完成的對象銷燬。
2、不要繼承沒有virtual析構函數的class,尤爲是標準庫的類。
3、若是一個class,不被任何類繼承,不要將其析構函數設爲virtual
有時,爲了方便,無論該類是否是基類(base class),都將其析構函數設爲virtual。可是,設爲virtual結構是有代價的。
class Point{ public: Point(int x, int y); ~Point(); private: int x, y; };
若是int是32bits,那一個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++的關鍵字,既然使用它,還談什麼和傳遞給C,C原本就不認識virtual。
4、若是class沒有任何一個純虛函數,卻又想讓該class爲abstract 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;
若是僅僅是這樣,運行程序,你會發現,程序報錯
這裏就涉及到析構函數的運行方式,最深層派生(most derived)的class的析構函數最早被調用,而後是每個base class的析構函數被調用。在本例中,當ddelete a時,Child的析構函數被調用,而後是AWOV的析構函數,可是AWOV的純虛析構函數並無實現,所以形成連接錯誤。
所以,就算析構函數被定義爲pure virtual,它也必定要有實現!從資源釋放的角度來看,析構函數是釋放資源和內存的關鍵,所以必需要實現它。
PS:爲何普通的方法聲明爲pure virtual後,就不要實現了呢?
1. 不須要實現
2. 實現也沒用
普通的方法聲明爲pure virtual,就是但願有子類區重載該方法,若是子類不重載該pure virtual方法,那子類也是抽象類,沒法被實例化。所以,必須有子類重載該pure virtual方法,一旦該pure virtual方法被重載,那它的實現根本不會被調用!
PS:構造函數必定不能是virtual,由於用到虛函數,就要使用虛函數表(Virtual Table)。但此時,構造函數並沒執行,當前對象還沒構造出來,所以虛函數表更是不存在。
這條Tips的含義就是,不要在析構函數中拋出異常。釋放對象的過程會調用析構函數(這點沒什麼好說的),若是連續釋放多個對象,而又有不止一個對象的析構函數拋出異常,就會致使程序中出現多個異常。事實上,在兩個異常的存在下,C++程序不是結束執行就是產生不明確結果。
1、對會拋出異常的操做,class應提供普通的方法,這樣客戶纔能有機會處理異常。
對於某些操做,當程序執行時,必須調用這些操做。例如,關閉數據庫的鏈接。通常爲了防止客戶忘了關閉鏈接,會將關閉操做放在析構函數中。根據Tips的含義,若是發生異常,析構函數只能使程序關閉,或者吞掉該異常,讓程序繼續執行。但這兩種方法,都會使客戶沒法對異常進行處理/響應
所以,惟一可行的辦法,就是讓客戶本身處理異常,一旦程序由於異常而不能正常運行,客戶就應該知道本身處理異常。因此,class應該爲會拋出異常的操做提供方法。
二、析構函數對於會拋出異常操做的處理方式。
有兩種處理方式
① 遇到異常就結束程序,用過abort完成。
try{ }catch (...){ std::abort(); }
② 遇到異常就吞下
try{ }catch (...){ 製做運轉記錄,記下對close的調用失敗 }