概述
在引入C++11后变量的初始化方式多种多样,对于每种初始化的方式的区别和联系是一个让我很迷惑地方
int x(0);
int y = 0;
int z{0};
int c = {0};
c++通常把c = {0}
这种初始化方式看成和z{0}
一样,那么x(0)
和y = 0
又有什么区别呢?,对于基本类型来说没有任何区别,对于自定义类型则不一样:
Widget w1(2); //调用的默认构造函数
Widget w2 = w1; //调用的是拷贝构造函数
w1 = w2; //调用的赋值操作符
而{}
这种初始化方式调用的是带有initializer_list
的构造函数,这就是{}
和()
的一个区别之处。
C++11中我们可以给类的成员变量之间赋初值,这个特性还是很友好的,但是你不能使用x(0)
这样的初始化方式。
int x{0};
int y = 0;
int z(0); //error
这就是{}
和()
的另外一个区别之处。在C++11中引入的std::atomic
是一个不可拷贝的对象,对于它的初始化是不能利用 y = 0
这种形式的,因为它会调用默认拷贝构造函数。
std::atomic<int> ail{0};
std::atomic<int> ai3 = 0; //error
y = 0
和 x(0)
这两种形式都有其不适用的地方,而z{0}
这种形式则都可以适用,这也就是为什么在c++11中这种初始化方式被称为统一初始化的原因吧。除此之外统一初始化的这种方式还可以避免窄化的转换。
double x,y,z;
int sum1{x + y + z}; //error 窄化转换,报错
int sum2 = x + y + z; //fine
int sum3(x + y + z); //fine
使用统一初始化的方式还有另外一个好处就是避免了C++复杂的语法分析。
Widget w2(); // 对于C++编译器来说需要区别这是一个函数声明还是一个变量的初始化
如果上面的w2
使用了{}
统一初始化的方式就避免了复杂的语法分析问题的产生。按照上面的分析我应该鼓励大家使用统一初始化,毕竟上面的这些优点还是很赞的,话又说回来了,C++什么时候有过没啥坑的特性了统一初始化也是一样,有一些不足之处,在Item2中介绍过对于统一初始化auto得到的类型是std::initializer_list
类型,此外还容易和普通的初始化方式产生不一致的行为。
class Widget {
public:
Widget(int i,bool b);
Widget(int i,double b);
Widget(std::initializer_list<long double> il);
};
Widget(10,true); //调用的是第一个构造函数,
Widget{10,true}; //按理应该是调用第一个构造函数,但是现在却调用了带初始化列表的构造函数
究其原因就是统一初始化是允许宽化转换的,所以上面10
和true
都转换成long double
了。更有甚者编译器会优先匹配std::initializer_list
即使不成功也会去匹配。
class Widget {
public:
Widget(int i,bool b);
Widget(int i,double b);
Widget(std::initializer_list<bool> il);
};
Widget w{10,5.0}; //error 10窄化转换成bool了
那岂不是只要使用了{}
进行统一初始化都会匹配带有std::initializer_list
的构造函数吗?,也不完全是这样因为int可以隐式转换成bool所以会优先匹配,如果没法转换了,那么还是会老老实实匹配普通的构造函数的。
class Widget {
public:
Widget(int i,bool b);
Widget(int i,double b);
Widget(std::initializer_list<string> il);
};
Widget w{10,5.0}; //匹配第一个构造函数,因为10和5.0都无法隐式转换成string
看完了上面的例子后,再来看一个边界情况的例子:
class Widget {
public:
Widget();
Widget(std::initializer_list<int> il);
};
Widget w1; // 调用默认的构造函数
Widget w2{} // 也是调用默认的构造函数
这下有点晕了,上面不是说了,在使用{}
这种方式进行初始化的时候选择的不是带有std::initializer_list
的构造函数吗?。这里怎么和上面说的不一致呢? 没办法,这是一个特例,如果你想让他调用带有初始化列表的构造函数,你需要像下面这样来调用它:
Widget w3({});
Widget w4{{}}; // ditto
在我们知道了{}
和()
的一些坑后,我们可以去看看标准库中的vector。
std::vector<int> v1(10,20); //使用的是非初始化列表的版本,10个元素,每个元素的值是20
std::vector<int> v2{10,20}; //使用的带初始化列表的版本,2个元素,值分别是10,20
如果不知道{}
和()
的一些不同的话,很容易认为上面两种形式是一致的,尽管{}
和()
初始化的方式有很多的不同,使得我们在使用的过程中会造成一定的困扰,但是只要我们保持一致这种困扰就会少了许多,避免{}
和()
初始化混杂在一起。
在一个模版中对于{}
和()
的选择更是无迹可寻,例如下面这个模版
template<typename T, // type of object to create
typename... Ts> // types of arguments to use
void doSomeWork(Ts&&... params)
{
// create local T object from params...
... }
对于上面的模版的函数体,可以替换成如下两种形式,但是对于传入的不同参数就会产生不可预期的结果
T localObject(std::forward<Ts>(params)...);
T localObject{std::forward<Ts>(params)...};
// 如果此时传入下面这代代码:
std::vector<int> v;
...
doSomeWork<std::vector<int>>(10, 20);
如果使用第一种方式就是创建10个元素,每个元素的值是20,如果是第二种形式就是创建两个元素10和20
对于上面这种情况,模版的作者其实也不知道预期的结果应该是什么样的,只有调用者是知道的,对于这类问题实在是没有很好的解决方案,只能通过注释的方式表明,标准库中的std::make_shared
、std::make_unique
具有同样的问题,他们使用()
初始化、并在代码中进行了注释。
最后
以上就是专注画板为你收集整理的Item7 Distinguish between () and {} when creating objects的全部内容,希望文章能够帮你解决Item7 Distinguish between () and {} when creating objects所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复