《Effective Modern C++》学习笔记之条款七:在创建对象时注意区分()和{}

对象初始化的方式有很多种,特别是C++11新引入了统一初始化,可以用于一切场合的初始化,至少从概念上可以这么理解。目前常见的初始化方式大概有以下几种:

int x(0);    //使用小括号初始化
int y = 0;   //使用等号初始化
int z{0};    //使用C++11新引入的统一初始化格式,即大括号初始化
int z1 = {0};//统一初始化格式变体,编译器对其处理完全无差别

C++11新引入了统一初始化方式可以处理很多C++98无法完成的工作,例如:

(1)给一个STL容器初始化为持有特定集合:

std::vector<int> v{1,3,5};

(2)为类的非静态成员变量指定默认初始化值:

class Widget {

private:
    int x{0};   //可行,C++11统一初始化
    int y = 0;  //可行,使用赋值语句赋值
    int z(0);   //不可行

}

(3)为不可赋值的对象进行初始化,例如std::atomic型别对象,不支持等号运算符,可以使用()或者{}进行初始化:

std::atomic<int> x1{0};  //可行
std::atomic<int> x1(0);  //可行
std::atomic<int> x1 = 0; //不可行

(4)它将直接禁止窄化型别转换:

double x = 2.3;

int sum{x};   //编译报错,会进行窄化转换检查

int sum2 = x; //编译告警,直接丢弃x的小数位

int sum3(x);  //编译告警,直接丢弃x的小数位

虽然使用大括号方式初始化的安全性和便捷性都很不错,但其也有自己的意外情况,在条款2时,我们就说过,如果使用auto来接收大括号初始化的变量,那么auto将被推导为std::initializer_list类型,所以使用大括号作为函数形参时,其类型是:std::initializer_list。这里就会引入一个问题,问题的原因是,编译器只要有任何可能把一个采用了大括号初始化语法的调用语句解读为带有std::initializer_list型别形参的构造函数,则编译器就会选用这个构造函数作为其初始化函数。例如:

class Widget {
public:
   Widget(int i,bool b);
   Widget(int i,double d);
   Widget(std::initializer_list<long,double> t);

}


Widget w1(10,true);  //使用小括号,正确调用第一个构造函数

Widget w2{10,true};  //使用大括号,将调用第三个构造函数,并将其强制转化为std::initializer_list<long,double>类型,10和true被转换为long和double

从上面可以看出,只要使用了大括号进行初始化,编译器一定想法设法的使用带有std::initializer_list型别形参的构造函数,但是如果我们声明了一个窄化的参数,编译器会怎么处理呢?

class Widget {
public:
   Widget(int i,bool b);
   Widget(int i,double d);
   Widget(std::initializer_list<bool,bool> t); //这里修改成bool类型来接收

}


Widget w1(10,true);  //使用小括号,还是正确调用第一个构造函数

Widget w2{10,5.0};  //使用大括号,尝试调用第三个构造函数,发现其bool类型无法容纳int和double类型,即使这样,编译器也不会调用第二个构造函数,而是直接提示编译错误

从上面可以看出编译器真的完美实践了什么叫做任何可能,那只有一种可能,大括号初始化时候不会被带有std::initializer_list型别形参的构造函数劫持,那就是,从形参到实参完全不可能转换:

class Widget {
public:
   Widget(int i,bool b);
   Widget(int i,stringd);
   Widget(std::initializer_list<std::string,std::string> t); //这里修改成string类型来接收

}


Widget w1(10,true);  //使用小括号,还是正确调用第一个构造函数

Widget w2{10,5.0};  //使用大括号,尝试调用第三个构造函数,int和double类型无论如何也转换不成string,将调用第二个构造函数

看到这里,我觉得这本书作者解释的不是很好,其实我们可以把使用大括号初始化时的形参看作是一个std::initializer_list型别,如果构造函数里面有std::initializer_list型别,可以隐式转换(如果是窄化,则编译报错),则调用带有std::initializer_list型别形参的构造函数,否则将调用其他备选构造函数。

要点速记

  • 大括号初始化可以应用的语境最为广泛,可以阻止隐式窄化转换,还可以对最令人头疼苦恼之解析语法免疫。
  • 在构造函数重载决议期间,只要有任何可能,大括号初始化就会与带有std::initializer_list型别形参的构造函数相匹配,即使有着其他貌似更合适的构造函数

猜你喜欢

转载自blog.csdn.net/Chiang2018/article/details/114108479