天天看點

C++的構造函數和析構函數

1、構造函數和析構函數為什麼沒有傳回值?

構造函數和析構函數是兩個非常特殊的函數:它們沒有傳回值。這與傳回值為void的函數顯然不同,後者雖然也不傳回任何值,但還可以讓它做點别的事情,而構造函數和析構函數則不允許。在程式中建立和消除一個對象的行為非常特殊,就像出生和死亡,而且總是由編譯器來調用這些函數以確定它們被執行。如果它們有傳回值,要麼編譯器必須知道如何處理傳回值,要麼就隻能由客戶程式員自己來顯式的調用構造函數與析構函數,這樣一來,安全性就被人破壞了。另外,析構函數不帶任何參數,因為析構不需任何選項。

如果允許構造函數有傳回值,在某此情況下,會引起歧義。如下兩個例子

C++的構造函數和析構函數
C++的構造函數和析構函數

如果c的構造函數可以有傳回值,比如int:int c():x(0) { return 1; } //1表示構造成功,0表示失敗

那麼下列代碼會發生什麼事呢?

c c = c();  //此時c.x == 1!!!

很明顯,c()調用了c的無參數構造函數。該構造函數傳回int值1。恰好c有一個但參數構造函數c(int i)。于是,混亂來了。按照c++的規定,c c = c();是用預設構造函數建立一個臨時對象,并用這個臨時對象初始化c。此時,c.x的值應該是0。但是,如果c::c()有傳回值,并且傳回了1(為了表示成功),則c++會用1去初始化c,即調用但參數構造函數c::c(int i)。得到的c.x便會是1。于是,語義産生了歧義。使得c++原本已經非常複雜的文法,進一步混亂不堪。

構造函數的調用之是以不設傳回值,是因為構造函數的特殊性決定的。從基本語義角度來講,構造函數傳回的應當是所構造的對象。否則,我們将無法使用臨時對象:

void f(int a) {...}  //(1)

void f(const c& a) {...} //(2)

f(c()); //(3),究竟調用誰?

對于(3),我們希望調用的是(2),但如果c::c()有int類型的傳回值,那麼究竟是調(1)好呢,還是調用(2)好呢。于是,我們的重載體系,乃至整個的文法體系都會崩潰。

這裡的核心是表達式的類型。目前,表達式c()的類型是類c。但如果c::c()有傳回類型r,那麼表達式c()的類型應當是r,而不是c,于是便會引發上述的類型問題。

2、顯式調用構造函數和析構函數

C++的構造函數和析構函數
C++的構造函數和析構函數

結果:

constructors

destructors    //這個是顯示調用的析構函數

destructors    //這個是delete調用的析構函數

這有什麼用?有時候,在對象的生命周期結束前,想先結束這個對象的時候就會派上用場了。直接調用析構函數并不釋放對象所在的記憶體。

由此想到的: 

new的時候,其實做了三件事,一是:調用::operator new配置設定所需記憶體。二是:調用構造函數。三是:傳回指向新配置設定并構造的對象的指針。

delete的時候,做了兩件事,一是:調用析構函數,二是:調用::operator delete釋放記憶體。

是以推測構造函數也是可以顯式調用的。做個實驗:

int main()

{

    myclass* pmyclass = (myclass*)malloc(sizeof(myclass));

    pmyclass->myclass();

    // …

}

編譯pmyclass->myclass()出錯:

error c2273: 'function-style cast' : illegal as right side of '->'operator

它以為myclass是這個類型。

解決辦法有兩個:

第一:pmyclass->myclass::myclass();

第二:new(pmyclass) myclass();

顯示調用構造函數有什麼用?

有時候,你可能由于效率考慮要用到malloc去給類對象配置設定記憶體,因為malloc是不調用構造函數的,是以這個時候會派上用場了。

另外下面也是可以的,雖然内置類型沒有構造函數。

int* i = (int*)malloc(sizeof(int));

new (i) int();

3、拷貝(複制)構造函數為什麼不能用值傳遞

當你嘗試着把拷貝構造函數寫成值傳遞的時候,會發現編譯都通不過,錯誤資訊如下:

error: invalid constructor; you probably meant 's (const s&)' (大緻意思是:無效的構造函數,你應該寫成。。。)

當編譯錯誤的時候你就開始糾結了,為什麼拷貝構造函數一定要使用引用傳遞呢,我上網查找了許多資料,大家的意思基本上都是說如果用值傳遞的話可能會産生死循環。編譯器可能基于這樣的原因不允許出現值傳遞的拷貝構造函數,也有可能是c++标準是這樣規定的。

如果真是産生死循環這個原因的話,應該是這樣子的:

C++的構造函數和析構函數
C++的構造函數和析構函數

當給s2初始化的時候調用了s2的拷貝構造函數,由于是值傳遞,系統會給形參st重新申請一段空間,然後調用自身的拷貝構造函數把s1的資料成員的值傳給st。當調用自身的拷貝構造函數的時候又因為是值傳遞,是以...

也就是說,隻要調用拷貝構造函數,就會重新申請一段空間,隻要重新申請一段空間,就會調用拷貝構造函數,這樣一直下去就形成了一個死循環。是以拷貝構造函數一定不能是值傳遞。

4、構造函數/析構函數抛出異常的問題

構造函數抛出異常:

    1.不建議在構造函數中抛出異常;

    2.構造函數抛出異常時,析構函數将不會被執行;

c++僅僅能删除被完全構造的對象(fully contructed objects),隻有一個對象的構造函數完全運作完畢,這個對象才能被完全地構造。對象中的每個資料成員應該清理自己,如果構造函數抛出異常,對象的析構函數将不會運作。如果你的對象需要撤銷一些已經做了的動作(如配置設定了記憶體,打開了一個檔案,或者鎖定了某個信号量),這些需要被撤銷的動作必須被對象内部的一個資料成員記住處理。

析構函數抛出異常:

    在有兩種情況下會調用析構函數。第一種是在正常情況下删除一個對象,例如對象超出了作用域或被顯式地delete。第二種是異常傳遞的堆棧輾轉開解(stack-unwinding)過程中,由異常處理系統删除一個對象。

在上述兩種情況下,調用析構函數時異常可能處于激活狀态也可能沒有處于激活狀态。遺憾的是沒有辦法在析構函數内部區分出這兩種情況。是以在寫析構函數時你必須保守地假設有異常被激活,因為如果在一個異常被激活的同時,析構函數也抛出異常,并導緻程式控制權轉移到析構函數外,c++将調用terminate函數。這個函數的作用正如其名字所表示的:它終止你程式的運作,而且是立即終止,甚至連局部對象都沒有被釋放。

概括如下:

    1.析構函數不應該抛出異常;

    2.當析構函數中會有一些可能發生異常時,那麼就必須要把這種可能發生的異常完全封裝在析構函數内部,決不能讓它抛出函數之外;

    3.當處理另一個異常過程中,不要從析構函數抛出異常;

    在構造函數和析構函數中防止資源洩漏的好方法就是使用smart point(智能指針),c++ stl提供了類模闆auto_ptr,用auto_ptr對象代替原始指針,你将不再為堆對象不能被删除而擔心,即使在抛出異常時,對象也能被及時删除。因為auto_ptr的析構函數使用的是單對象形式的delete,而不是delete [],是以auto_ptr不能用于指向對象數組的指針。當複制 auto_ptr 對象或者将它的值賦給其他 auto_ptr 對象的時候,将基礎對象的所有權從原來的 auto_ptr 對象轉給副本,原來的 auto_ptr 對象重置為未綁定狀态。是以,不能将 auto_ptrs 存儲在标準庫容器類型中。如果要将智能指針作為stl容器的元素,可以使用boost庫裡的shared_ptr。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

http://www.cnblogs.com/luxiaoxun/archive/2012/09/06/2673249.html

繼續閱讀