讀【深度探索C++物件模型】【中】

來源:互聯網
上載者:User

【構造和解構函式】

通常我們的看法是:當定義一個類的時候,如果沒有為它寫一個建構函式,系統將幫我們產生一個,並完成成員的初始化。但是,從編譯器來看,上述看法中的兩點認識都不夠正確。編譯器只會在編譯需要的情況下(nontrivial的條件)自動產生預設建構函式建構函式。一般包括下面四種情況:1.類中包含的資料成員有預設建構函式;2.其基類包含預設建構函式;3.具有虛成員函數;4.虛繼承至某個類。其他情況被稱為是trivial,編譯器將不會添加一個預設建構函式。把握好編譯器需要才添加這個前提,很容易理解為什麼要添加預設建構函式。預設函數完成編譯器需要做的一些事情,包括初始化基類,虛指標等。搞清楚這個概念有助於瞭解建構函式的存在意義。建構函式是為了完成對象的初始化(即初始化分配來的空間),與空間的分配沒有關係,而我們會經常自覺不自覺的混淆這個概念,在處理一些問題(比如new操作符)的時候,理解不夠清晰。現在瞭解了編譯器的原理,可以加深對建構函式的認識。

另外如果一個類中被定義了一個建構函式,編譯器也會根據它的需求擴充該建構函式。考慮下面一段代碼:

class A
{
 virtual void aMethod() {}
};

class B :
 public A
{
public:
 A(int i) : aValue(i) {}

 virtual void aMethod() {}

private:
 int aValue;
};

它的建構函式會被擴充成大概下面的樣子:

 A* A::A(A* this, int i)
{
 // 1.如果有虛基類會被先行調用

 // 2.基類的建構函式被調用(可視為遞迴過程)
 B::B();

 // 3.初始化虛指標

 // 4.展開初始化列表,如果有資料元素有預設建構函式將被調用
 aValue(i);

 // 5.建構函式實體
}

請注意虛表和虛指標被初始化的位置。它處於基類建構函式被調用後,本身內容被執行之前。也就是如果在該類基類的建構函式中放置一個虛函數,它執行的內容是定義在基類本身的版本(也許你想調用子類的實現版本),這個概念與其他的虛函數的調用不一樣(其他調用的都是屬於子類的版本)。因為在基類構造的時候,虛指標依然指向基類的虛表,只有初始化虛指標後才會產生子類的虛表。這中虛表構建的方式和其他一些語言會有所不同(比如C#),所以當你在你的建構函式中放置虛函數時(通常不建議這樣做),請保證你是真的要使用本身版本的虛函數。

預設解構函式與預設建構函式的問題類似。即,只有在編譯期需要的時候產生,目地是完成清理的工作,與空間回收無關。而且,解構函式並不一定要於建構函式配對。比如類中只是有一個虛函數的時候,就不必產生預設解構函式。所有的析構和構造對稱出現的想法,都只是我們出於美學的主觀臆斷罷了。而對解構函式的擴充,其順序與建構函式相反,這一點倒是符合了對稱性。

 

【拷貝建構函式】

 C++的每個類型中都有一個拷貝建構函式(伴隨著一個複製操作符),如果類中沒有顯性的聲明,系統會安插一個(想一想,為什麼是必須添加的),以完成由一個已有對象複製產生新的對象。拷貝建構函式可以分成兩種。一種是按位拷貝(bitwise copy),另一種是按成員拷貝(member copy)。所謂按位拷貝就是指把一個對象在記憶體中的每一位,一一拷貝到另一個新的位置,它的特點是拷貝速度快。而按函數拷貝就是分別調用各成員的拷貝建構函式,依次拷貝所需的成員,其特點是控制力強,可以選擇需要拷貝的成員,或者對成員做特殊的處理。系統自動產生的拷貝建構函式通常是按位拷貝,但在某些情況(這些情況和上面產生建構函式的情況類似)下會採用按成員拷貝。

為什麼需要按函數拷貝,舉個例子就理解這個問題。假設下面這樣一個繼承體系:設定B類是A類的子類,且A, B的對象大小不等,並B重載A的一個虛方法。當一個B對象拷貝到另一個B對象中,按位拷貝不會出現問題。但如果把一個B對象拷貝到一個A對象上,再簡單的應用按位拷貝就會出現問題。可能vptr被切割掉了(如果vptr放在對象的最後),要不即使不被切割掉也不會被正確設定(A對象和B對象的虛指標指向了同一個虛表)。因此在這種場合下,按位拷貝就不夠用了,系統會安插一個具有成員拷貝的拷貝建構函式,對虛指標的拷貝問題進行特別的處理。

理解這個問題的益處在於可以協助寫出更有效代碼。通常我們寫類的拷貝建構函式都會採用成員拷貝的方式。但其實很多時候更好的方式是先做一個按位拷貝,再修改不符合按位拷貝的內容。這樣做不僅編碼簡單清晰,啟動並執行也更加高效。

理解該問題另一個益處是協助我們理解自訂拷貝建構函式的時機。編譯器安插的拷貝建構函式,無論是哪種方式,都是一個淺拷貝。因為這對於編譯器來說,以足夠保證它不會出錯了。但如果我們類型中涉及到指標類型的成員時候,我們可能需要的是一個深拷貝。這對於編譯器來說不重要,但對於保證我們所需的程式邏輯是很重要的。只有在這種編譯器無法滿足需求的情況下才使用自訂拷貝建構函式,除此之外都不用畫蛇添足。因為不但增加了工作量,還很有可能降低了效率(當用成員拷貝頂替了位拷貝時)。

 

【動態分配】

眾所周知,C++在運行期間在堆上管理記憶體通常採用new和delete操作符。但new操作有時候不僅僅是等價於調用new操作符以完成空間的分配。比如int *pi = new int(5)。它相當於先調用new操作符:int *pi = __new(sizeof(int)),用以完成空間分配;再調用*pi = 5,用以完成初值設定。而深入一些來說,new操作符所作的記憶體配置也不是簡單的按需分配。當分配失敗的時候,會調用new_handler來進行一些處理(可能包括回收空間,輸出資訊等等),然後再重新分配直至分配成功或new_handler不再處理位置。因此,我們有時候只需要重寫new_handler的行為就可以達到我們的目的(比如自訂重分配方式),而不再需要重寫new操作符,這樣降低了工作的難度。只有真正需要改變記憶體配置策略的時候,才需要重載new操作符。

相比較delete操作符所做的事情比較純粹,就是釋放對象佔用的堆中的空間。但我們如何知道對象佔用了多大的空間,我們該釋放多少空間呢?常規的做法是在new的時候安插一個cookie,裡面存放對象大小等資訊,在delete的時候,函數訪問該cookie,重而保證正確的釋放。很明顯,cookie的插入增加了對象的大小,降低了操作的效率,還會出現安全性的問題。所以當程式中有大量的小對象存在,或者是需要頻繁擷取和釋放空間的時候,往往會常用一些更具技巧性的分配方案,以滿足需求(比如,池化分配)。

數組的分配與單個對象的分配問題類似,可以簡單的視為連續分配n個對象空間。只是為了保證delete的正確執行,在數組對象分配的時候,還需要多安插一些資訊。

而除了常見的new操作符外,C++還支援另一種被稱為Placement operator new的操作符,它多接受一個void*指標類型,預設情況下,是用於在指定地址開始分配空間(更確切的說法應該在該子句前加上優先)。當然也有的時候,通過重載,void*也會指向其他對象(比如ostream對象),用已完成其他的特殊功能(比如書寫日誌)。不論哪種,都需要保證placement new和placement delete成對出現,這樣才能完成正常的空間回收。

 

【變數管理】

當在一個函數中構造了一個對象(棧中),會在離開函數域的時候析構這個對象。如果在函數頭構造了該對象,而該函數有多個出口(return語句),編譯器就會在每個出口前調用該對象解構函式。因此,我們最好在第一次使用該對象前構造該對象,這不僅僅是一種良好的編程習慣,有利於閱讀和理解,並且也是一種減少無謂的代碼損失的好方法。

另外一個特殊變數是全域變數。程式中任何代碼調用全域變數的時候,都要保證全域變數已經按要求完成初始化。因此,系統會需要在進入main函數後首先初始化全域變數,在離開main函數前,清理全域變數。一種實現方法,是在編譯每個全域變數的時候,為該變數添加一個用於構造的函數(eg. __sti_xxx())和用於釋放的函數(eg. __std_xxx())。完成編譯後,還需要收集所有用於全域變數構造和釋放的函數把它們放入兩個特殊的函數中(eg. _main()和exit())。把它們分別放在main()函數的入口和出口處。用於在進入main後初始化和離開main前解構。收集的具體實現取決於編譯器,如果編譯器支援跨平台性,對檔案的操作要特別小心,避免調用平台檔案格式相關的作業碼。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.