C++的建構函式和解構函式

來源:互聯網
上載者:User
建構函式提供了一種機制,通過它有機會完成必要的初始化工作,從而使對象成為有意義
的存在物,而不僅僅只是一塊原始的空間。

但是,我們逐漸瞭解到,建構函式具有的地位,不僅對於使用者(程式員),對於編譯器履
行職責也極為重要。通過這個機制,它讓C++的一些基本的特性,如繼承、多態得到了正確
的貫徹和表現。

首先不難理解的一點是在建構函式中,要確保基類對象的正確構造,如果是從基類繼承的
話。因為繼承類對象至少可以被“低”看為一個基類對象,具有後者的所有行為和表現,
所以基類的建構函式首先被調用。如果所謂的基類也是從其他類繼承過來的,這就形成了
一個調用鏈。最後的情況是,最基礎的類的建構函式首先被執行,然後才是上一層的構造
函數,如此到最外層的繼承類。這個過程必須是嚴格有序的。如果沒有這個次序保證,繼
承類就有機會在基類還沒構建好的情況下就訪問基類的資料或函數,這將導致不可預料的
、災難性的後果。

只有首先確保基類的正確構造,接下來才能進行繼承類本身的構造。因為類中可能含有成
員對象,必須保證這些對象也被構造,按照一定的次序(一般就是變數聲明的次序)。可
能其中一個或一些成員對象的構造需要參數,這需要在類的建構函式中提供所有需要的參
數(構成所謂顯式的“初始化列表”)。

看起來這就是建構函式的全部隱含(或半隱含)工作?不是的。我們忽視了一個極為重要
的東西,我稱之為類關聯資訊(表)。類關聯資訊是編譯時間產生的、為運行時所需的類的
附加資料(可以認為這些資料放在全域資料區中)。我們熟悉的虛函數表(v-table),我
把它歸在類關聯資訊的範疇之中(最初,我以為虛函數表就是全部,但這個理解有點狹義
)。此外,還包含運行時類型資訊(RTTI)。此外,不排除我們還不清楚的其它輔助資料
(總之,類關聯資訊是一種廣義的、統一的稱呼)。如果編譯器為類產生了類關聯資訊,
那麼毫無疑問,必須在建構函式中將它與當前對象(類的執行個體)關聯起來(也許簡單到只
需要設定一個指標即可)。

例如,如果類中含有虛函數,或者,它覆蓋了基類中的虛函數(兩種情況下都意味著類有
自己的虛函數表)。那麼,設定正確的關聯後,將存在一個指標(v-ptr),它正確地指向
了該虛函數表(v-table)。此後,多態才能表現出所期望的正確行為。

再次指出(次序的重要性),設定關聯,或者狹義地說,設定v-ptr必鬚髮生在對基類構造
函數的調用之後。因為,繼承類如果有自己的虛函數表,那麼v-ptr會被改寫,以指向該表
,即使此前v-ptr已經被基類所設定。這是合法的,也正是所期望的。但是語義上我們絕不
允許基類可以改寫繼承類所設定的v-ptr。如果v-ptr設定發生在基類構造調用之前,那麼
這種非法的一幕就會發生。

所有上述的事情完成之後(對於使用者來說它們幾乎是隱含的),才真正開始執行使用者的初
始化代碼。

在建構函式中調用虛函數,會發生什嗎?發生的情況也許是始料未及的。當前類的對象正
在構建,v-ptr指向的是當前類的虛函數表。此時,還沒到繼承類執行它自己的代碼(如設
置v-ptr,執行初始化代碼)的時候,那一切發生在當前類的建構函式執行完畢之後。所以
,將要執行的是本類的虛函數版本,而不是可能被覆蓋的繼承類的版本。這裡有一個反面
的證據。假如v-ptr設定發生在基類建構函式調用之前,讓我們有機會調用繼承類的虛函數
版本,這意味著什嗎?繼承類還沒有完成初始化(因而對象還沒有構建好),我們企圖在
一個沒有構建好的對象上執行它的成員函數,可以想見後果是災難性的。

類的建構函式何時被調用?在對象被建立的時候。對象可能位於棧上,全域資料區,或堆
上。對象可能會在聲明的地方建立,這樣的對象位於棧上或全域資料區。對象也可以使用
new操作符動態地建立,這樣的對象將位於堆上。

解構函式
解構函式提供了一種和建構函式相反的機制,允許在銷毀一個對象之前(亦即回收對象所
佔用的空間),讓對象釋放自己所使用的資源。再次,這裡所關心的是語言底層所發生的
事情。

與建構函式的執行次序剛好相反,解構函式從最外層的類開始執行,最基礎的類的析構函
數最後執行,看起來就象一層層的剝殼。這個次序要得到嚴格保證的理由也是明顯的(違
反次序又將產生相關性問題)。結果看起來是這樣的:使用者析構代碼->成員對象析構->基
類解構函式。

解構函式在對象行將被銷毀時調用。當對象超出其範圍,例如一個函數內部的局部變數
,在函數返回時將被自動銷毀。對於在堆上建立的對象,我們只能通過對象指標p,執行d
elete p來銷毀它。現在就有一個問題。p指向了某個類型(例如A)的對象,但是卻可能是
通過上溯造型(upcast)得來的,因此它指向的實際上是一個B的對象,那麼將發生什嗎?

顯然,正確的做法是把對象作為B的執行個體來銷毀。也就是說,調用B的解構函式。我們也許
很快想到,首先,在運行時刻可以知道這個對象“實際上”是什麼類型(那個最晚的派生
類,本例中假定就是B),例如通過RTTI。然後,根據實際的類型,“找到”並調用該類的
解構函式。然而,這個想法不會自然實現,需要在編譯時間把類的類型資訊和一個指向析構
函數的指標關聯起來。不過,上述考慮似乎複雜了點。我們可以把這個解構函式指標放入
虛函數表中(某個特別的位置)。理由是,與虛函數非常相似,儲存這個解構函式指標的
slot可以為繼承類所覆蓋(從而指向繼承類的解構函式),而且繼承類應該總是覆蓋它(
然而下面將看到,實際的情況與“總是覆蓋”有出入)。

無論怎樣,解構函式指標是儲存在前面稱之的類關聯資訊(表)中,編譯器不難找到。因
而當通過對象指標(即使已經上溯造型)執行刪除操作時,總是能夠調用正確的解構函式
。但是,在多個編譯器的實驗發現,只有把基類的解構函式聲明為虛的,繼承類才會覆蓋
它。看來的確採用虛函數的技術來對待解構函式了。如果基類的解構函式沒有聲明為虛的
,則執行前面的delete p時,只有A的解構函式得到執行,而B的沒有執行!

這是令人驚訝的。竟然允許“不完全”析構的情況發生,把選擇權以及責任交給了程式員
!我不能確定這麼做的主要理由是什麼。也許還是因為C++如此看重效率,它迫使程式員在
必要的時候顯式聲明他的需求,因為類似虛函數的間接調用要佔用額外的空間和時間。

前面考察了虛函數在建構函式中的行為,繼續來看一下在解構函式中的情形。因為析構過
程是自外向裡的,當前類在調用虛函數的時候,其繼承類此前已經完成了析構,不再是可
用的。因而,是不可以調用繼承類的虛函數版本的。結果,調用的仍然是本地版本(和構
造函數中的結論一樣,虛函數機制被忽略)。

Refer to <Thingking in C++> ,Bruce Eckel!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.