建構函式與析構函式

2021-09-07 03:25:43 字數 3543 閱讀 9362

1、建構函式和析構函式為什麼沒有返回值?

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

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

class c

c(int i): x(i)

private:

int x;

};

如果c的建構函式可以有返回值,比如int:int c():x(0) //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、顯式呼叫建構函式和析構函式

#include using namespace std;

class myclass

~myclass()

};int main()

結果:

constructors

destructors    //這個是顯示呼叫的析構函式

destructors    //這個是delete呼叫的析構函式

這有什麼用?有時候,在物件的生命週期結束前,想先結束這個物件的時候就會派上用場了。直接呼叫析構函式並不釋放物件所在的記憶體。

由此想到的: 

new的時候,其實做了三件事,一是:呼叫::operator new分配所需記憶體。二是:呼叫建構函式。三是:返回指向新分配並構造的物件的指標。

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

所以推測建構函式也是可以顯式呼叫的。做個實驗:

int main()

編譯pmyclass->myclass()出錯:

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

它以為myclass是這個型別。

解決辦法有兩個:

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

第二:new(pmyclass) myclass();

第二種用法涉及c++ placement new 的用法。參考:

顯示呼叫建構函式有什麼用?

有時候,你可能由於效率考慮要用到malloc去給類物件分配記憶體,因為malloc是不呼叫建構函式的,所以這個時候會派上用場了。

另外下面也是可以的,雖然內建型別沒有建構函式。

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

new (i) int();

3、拷貝(複製)建構函式為什麼不能用值傳遞

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

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

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

如果真是產生死迴圈這個原因的話,應該是這樣子的:

class s

s(const s st) //拷貝建構函式

private:

int a;

};int main()

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

也就是說,只要呼叫拷貝建構函式,就會重新申請一段空間,只要重新申請一段空間,就會呼叫拷貝建構函式,這樣一直下去就形成了乙個死迴圈。所以拷貝建構函式一定不能是值傳遞。

4、建構函式/析構函式丟擲異常的問題

建構函式丟擲異常:

1.不建議在建構函式中丟擲異常;

2.建構函式丟擲異常時,析構函式將不會被執行;

c++僅僅能刪除被完全構造的物件(fully contructed objects),只有乙個物件的建構函式完全執行完畢,這個物件才能被完全地構造。物件中的每個資料成員應該清理自己,如果建構函式丟擲異常,物件的析構函式將不會執行。如果你的物件需要撤銷一些已經做了的動作(如分配了記憶體,開啟了乙個檔案,或者鎖定了某個訊號量),這些需要被撤銷的動作必須被物件內部的乙個資料成員記住處理。

析構函式丟擲異常:

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

在上述兩種情況下,呼叫析構函式時異常可能處於啟用狀態也可能沒有處於啟用狀態。遺憾的是沒有辦法在析構函式內部區分出這兩種情況。因此在寫析構函式時你必須保守地假設有異常被啟用,因為如果在乙個異常被啟用的同時,析構函式也丟擲異常,並導致程式控制權轉移到析構函式外,c++將呼叫terminate函式。這個函式的作用正如其名字所表示的:它終止你程式的執行,而且是立即終止,甚至連區域性物件都沒有被釋放。

概括如下:

1.析構函式不應該丟擲異常;

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

3.當處理另乙個異常過程中,不要從析構函式丟擲異常;

建構函式與析構函式

建構函式 主要作用就是為物件初始化。有一點要說的是,在繼承體系彙總,如果在建構函式中,如果沒有指定基類的建構函式,那麼編譯器會在建構函式開頭加入,基類的預設建構函式,這樣就可以初始化基類物件部分 析構函式 對於析構函式,要說的多點,實際也不太複雜,就是加入了virtual 使其具有了多型性質 inc...

建構函式與析構函式

建構函式 先看看建構函式的呼叫順序規則,只要我們在平時程式設計的時候遵守這種約定,任何關於建構函式的呼叫問題都能解決 建構函式的呼叫順序總是如下 1.基類建構函式。如果有多個基類,則建構函式的呼叫順序是某類在類派生表中出現的順序,而不是它們在成員初始化表中的順序。2.成員類物件建構函式。如果有多個成...

建構函式與析構函式

概述 乙個類有兩種特殊的成員函式 建構函式與析構函式。建構函式功能是在建立物件時,給資料成員賦初值,即物件的初始化。析構函式的功能是釋放乙個物件,在物件刪除前,用它做一些記憶體釋放工作,與建構函式的功能相反。建構函式 在物件建立時它會被自動執行,因此變數 物件的初始化 一般都放在建構函式中。1 物件...