c++11后引入了uniform initialization的概念,按照它说的,任何的初始化操作都可以借由大括号{}
搞定。
比如在c++98时代,我们会用:
int i[] = {1,3,4,5,6}; //初始化数组
class Cat
{
public:
Cat(int old, int weight):old(old),weight(weight){};
int old, weight;
};
Cat c1(12,32); //初始化类实例
Dog d1= 13; // 参数化式地初始化类实例(单参数)
Dog d2 = d1; // 拷贝构造函数初始化类示例
总之,初始化不同的东西,有各种各样的语法,c++11干脆把所有的初始化都用{}
实现吧。
int i[]{1,2,3,4};
Cat c1{12,32};
// Dog类似
Dog d1{d3};
Dog d2{d1};
坑,大坑
似乎一切岁月静好,无脑用大括号就完事了。但考虑下面这样的情况:
vector<int> vi{2,3}; //执行完这句,vi里是2,3还是3,3呢?
c++98的程序员知道vector有个构造函数是这样的:
vector( size_type count, const T& value, const Allocator& alloc = Allocator());
就是说初始化时填入count个value。
那么按照大括号里的东西就是构造函数的参数的逻辑,vi构造完的结果是3,3。
但是实际上是2,3。太坑了,太坑了。
如果我们想弄出3,3。得写成
vector<int> vi(2,3);
怎么一会儿大括号里是构造函数的参数,一会儿又不是,烦死了。
解释
具体原因就是编译器看到被大括号包起来的东西,会将其转化成initializer_list
{1,2,3} --------> initializer_list<int>
即编译器见到前者,就会把其转换成后者。
如果类没有以initializer_list{}
和()
没有任何区别,该调用哪个调用哪个,直接将{}
看成()
就完事。
但是,如果类有initializer_list
重点来了,为什么vector
373 vector(initializer_list<value_type> __l,
374 const allocator_type& __a = allocator_type())
375 : _Base(__a)
376 {
377 _M_range_initialize(__l.begin(), __l.end(),
378 random_access_iterator_tag());
379 }
既然有这个构造函数,遇见{}
时就该调用它,把initializer_list一整包丢进去。而非调用像上面的
vector( size_type count, const T& value, const Allocator& alloc = Allocator());
相关知识
c++11中{}
语法的出现,也导致了多实参非explicit ctor构造函数可能会在隐式转换时调用。在以前,只有one argument non-explicit ctor才有可能会在隐式转换时调用,因此我们加个explicit就阻止了这种转换。但是c++11后编译器会将{}
自动解包(如果没有initializer_list为参的构造函数时),可能解包出来多个东西。因此将multi-argument ctor前面加上explicit就变得有必要了。
一些bb
我觉得这个设计有些问题,当用第三方类库的时候,鬼知道它有没有写initializer_list为参的构造函数。本来我只是想开开心心用{}
的语法调用普通构造函数,结果不小心调用了initializer_list的版本,佛了。感情我写{}
之前还得看看类库的源代码是吧。
最后我想出了一个解决办法,估计也是设计者想让我们用的吧,就是正常创建类对象时,用()
就完事。只有真正当我知道该类(例如stl容器)提供了初始化列表的版本,并且我知道其行为,并且我真的需要它的时候,我才用{}
。