C++学习笔记(1):在默认构造函数内部使用带参数的构造函数
题目
以下代码的输出是不是0:
#include <unordered_map>
#include <iostream>using namespace std;struct CLS{int i;CLS(int i_) :i(i_){}CLS(){CLS(0);}
};int main(){CLS obj;std::cout << obj.i << endl;return 0;
}
结果
-858993460
为什么呢?
定义对象的流程
以下内容参考自:从一道题谈C++中构造函数调用构造函数 - 中土 - 博客园 (cnblogs.com)
代码奇怪的地方在于构造函数中调用了自己的另一个构造函数,我们知道,当定义一个对象时,会按顺序做2件事情:
1)分配好内存(非静态数据成员是未初始化的)
2)调用构造函数(构造函数的本意就是初始化非静态数据成员)
显然上面代码中,CLS obj;这里已经为obj分配了内存,然后调用默认构造函数,但是默认构造函数还未执行完,却调用了另一个构造函数,这样相当于产生了一个匿名的临时CLS对象,它调用CLS(int)构造函数,将这个匿名临时对象自己的数据成员m_i初始化为0;但是obj的数据成员并没有得到初始化。于是obj的m_i是未初始化的,因此其值也是不确定的
验证:在析构函数中打印一个destroy,结果如下,可见生成了两个对象。
~CLS(){cout << "destroy" << endl;}destroy
-858993460
destroy
从这里,我们归纳如下:
在c++里,由于构造函数允许有默认参数,使得这种构造函数调用构造函数来重用代码的需求大为减少
如果仅仅为了一个构造函数重用另一个构造函数的代码,那么完全可以把构造函数中的公共部分抽取出来定义一个成员函数(推荐为private),然后在每个需要这个代码的构造函数中调用该函数即可
解决方案:placement new
最好的方法当然是不调用,但偶尔我们还是希望在类的构造函数里调用另一个构造函数,可以按下面方式做:
在构造函数里调用另一个构造函数的关键是让第二个构造函数在第一次分配好的内存上执行,而不是分配新的内存,这个可以用标准库的placement new做到
inline void *__cdecl operator new(size_t, void *_P)
{return (_P);
}
placement new即为定位new,当需要在制定内存而不是新开辟一个内存构建新对象时,就使用之
格式:
address:placement new所指定的内存地址
ClassConstruct:对象的构造函数
Object * p = new (address) ClassConstruct(...);
于是代码修改为:
#include <unordered_map>
#include <iostream>using namespace std;struct CLS{int i;CLS(int i_) :i(i_){}CLS(){new (this)CLS(0);} // 使用placement new
};int main(){CLS obj;std::cout << obj.i << endl;return 0;
}
这时的输出就是0了:
0
destroy