1,引子:
以下代码中的输出语句输出 0 吗,为什么?
struct Test {int _a;Test(int a) : _a(a) {}Test() {Test(0);
}
};
Test obj;
cout << obj._a << endl;
输出为:-858993460
2,剖析
上面代码的输出为一个垃圾值,也就是说 obj 调用构造函数并没有对成员进行初始化工作,虽然默认无参构造 Test() 内部调用了 Test(int a),但从结果看,初始化工作并不成功.这是为什么呢?
在执行构造函数时,Test() 并不会调用"this"对象(即 obj 对象)的 Test::Test(int a),而是会用 Test::Test(int a) 来创建一个新的临时实例对象,然后当这条语句执行完后,这个新的临时对象马上就会被销毁.这样一来,"this" 对象就没有被初始化,成员_a 就是垃圾值,以后使用 "this" 对象就有可能产生一些问题.
3. 重点:构造函数互相调用
分析完这个题目之后,我们会想到另一个问题.也是我们今天重点关注的问题:
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)的赋值初始化工作,可以这样写:
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 个参数的构造函数,然后在执行一些自己的代码,这就如同派生类先调用基类的同名函数,再执行自己特有的代码.但是这种机制如何实现呢?
之前我们得出过结论:构造函数调用另一个构造函数并不能完成当前对象的初始化工作,只是初始化了临时对象.下面我们就进入本文的核心问题:如何在构造函数中调用本类的另一个构造函数来初始化当前对象?
方法一:使用 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 标准中的新特性--).目前只能再 VS2013 及以上的版本使用,这个方法局限性很大,不过确实很方便.
来源: http://www.cnblogs.com/Ray1024/p/5760278.html