一、引子:html
如下代碼中的輸出語句輸出0嗎,爲何?函數
struct Test { int _a; Test(int a) : _a(a) {} Test() { Test(0); } }; Test obj; cout << obj._a << endl;
輸出爲:-858993460this
二、剖析spa
上面代碼的輸出爲一個垃圾值,也就是說obj調用構造函數並無對成員進行初始化工做,雖然默認無參構造Test()內部調用了Test(int a),但從結果看,初始化工做並不成功。這是爲何呢?htm
在執行構造函數時,Test()並不會調用"this"對象(即obj對象)的Test::Test(int a),而是會用Test::Test(int a)來建立一個新的臨時實例對象,而後當這條語句執行完後,這個新的臨時對象立刻就會被銷燬。這樣一來,"this"對象就沒有被初始化,成員_a就是垃圾值,之後使用"this"對象就有可能產生一些問題。對象
3.重點:構造函數互相調用blog
分析完這個題目以後,咱們會想到另外一個問題。也是咱們今天重點關注的問題: 內存
class Test { int _a; int _b; int _c; public: Test(int a, int b) : _a(a), _b(b),_c(0) {} Test(int a, int b, int c); };
若是咱們C++類中有兩個構造函數,分別爲Test(int a, int b)和Test(int a, int b, int c)。若是咱們的構造函數Test(int a, int b, int c)要完成全部成員(a,b,c)的賦值初始化工做,能夠這樣寫:ci
Test::Test(int a, int b, int c) : _a(a) , _b(b) , _c(c) { }
可是,這樣寫又重複了構造函數Test(int a, int b)的工做,類成員少的狀況下還好,若是成員很是多,重複寫的話代碼量過大,並且代碼可讀性下降了。然而咱們能夠看到構造函數Test(int a, int b)已經完成了成員a和成員b的賦值初始化工做,爲了減小代碼量,就想着讓3個參數的構造函數調用2個參數的構造函數,而後在執行一些本身的代碼,這就如同派生類先調用基類的同名函數,再執行本身特有的代碼。可是這種機制如何實現呢?get
以前咱們得出過結論:構造函數調用另外一個構造函數並不能完成當前對象的初始化工做,只是初始化了臨時對象。下面咱們就進入本文的核心問題:如何在構造函數中調用本類的另外一個構造函數來初始化當前對象?
方法一:使用placement new技術,在3個參數中顯式調用2個參數的構造函數。
3參數構造函數能夠這樣實現:
Test::Test(int a, int b, int c) { new (this) Test(a, b); ... }
構造函數分爲2個執行階段:一是在初始化列表的初始化階段,二是在構造函數體內的賦值階段。上述方法是在第二個階段調用2個參數的構造函數。
placement new是operator new的一個重載版本,只是咱們不多用到它。若是你想在已經分配的內存中建立一個對象,使用new是不行的。也就是說placement new容許你在一個已經分配好的內存中(棧或堆中)構造一個新的對象。原型中void*p實際上就是指向一個已經分配好的內存緩衝區的的首地址。placement new技術的形式是 new(void *p) Type(...),表示在p所指的內存區域調用Type構造函數,該過程沒有內存請求。
這個方法本質就是在對象地址處,調用2個參數的構造函數從新生成一個新的對象而後覆蓋該對象。這個實現方法有投機取巧的嫌疑。
方法二:使用C++11新特性——委託構造函數(Delegating constructors)。能夠在構造函數初始化列表直接調用,相似於調用基類構造函數。
Test::Test(int a, int b, int c) : Test(a, b) { ... }
上述說了構造函數有2個執行階段,該方法是在第一個階段進行的,更加方便。可是注意不能在Test(a, b)後面在接_c(c)了,由於調用2個參數的構造函數以後,就至關於該對象已經初始化完成了,不能在初始化列表放入其餘成員的初始化形式。只能放在構造函數體中的賦值階段。該方法目前只能用在VS2013中。
這個方法利用了C++11標準中的新特性——委託構造函數(Delegating constructors)。目前只能再VS2013及以上的版本使用,這個方法侷限性很大,不過確實很方便。