一、引子: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及以上的版本使用,这个方法局限性很大,不过确实很方便。