Effective Modern C++ 读书笔记:条款07

Aki 发布于 2022-11-27 315 次阅读


07、在创建对象时注意区分{}和()

常见的四种初始化方法:

int x(0); 
int y = 0; 
int z{0}; 
int z = {0}; 

大括号初始化的特性:

1)指定容器内的内容

vector<int> v = {1,2,3,4,5,6}; //这个原理是初始化列表

vector<int>(10,20);
vector<int>{10,20}; //注意这两个方法是不同的

2)为非静态成员指定默认初始化值

class widget{ 
... 
private: 
    int x{0};  // 可行 
    int y(0);  // 不可行 
    int y = 0;  //可行
}; 

3)可以为不可复制的对象进行初始化

atomic<int> a{0}; 
atomic<int> b(0); 
atomic<int> c = 0; // 不可行 

class Test
{
  Test(){}
  Test(const Test&rhs) = delete;
};

Test t{};

4)防止隐性窄化型别转换

double a{1.1},c{2.2};
int b{a};  // 错误,会报错
int b(a+c);  //可行,会发生隐式类型转换,double变int,丢失精度
int b = a+c;  // 可行,会发生隐式类型转换,double变int,丢失精度

窄化是指将信息量较大的类型转换为信息量较小的类型,因此会在转化过程中丢失一定的信息。大括号初始化可以防止隐性窄化类型转换的发生。

5)明确解析语法

class Test
{
  Test(){}
};

Test t();  //error
Test t{};  //正确

有时候我们想调用默认构造函数生成一个对象,有可能不小心写成上面的样子Test t(),会导致声明了一个返回值为Test类对象的函数出来。

使用大括号调用默认构造函数就不会有这种问题,Test t{}。

6)大括号初始化的缺陷

虽然使用大括号方式初始化的安全性和便捷性都很不错,但其也有自己的意外情况,在条款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<double> t);

}

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

Widget w2{10,true};  //使用大括号,将调用第三个构造函数,并将int的10和bool的true强制转换为double类型

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

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

}


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

Widget w2{10,"hello,world"};  //使用大括号,尝试调用第三个构造函数,int类型无论如何也转换不成string,const char*可以转换成string类型,因此将调用第二个构造函数

请记住:

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