对象初始化的方式有很多种,特别是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的小数位

(5)对解析语法免疫

例如我们本来想以默认方式构造一个对象,但因为解析语法原因,导致变成了声明一个函数。例如:

// 本意是调用Widget的默认构造函数,声明一个对象
// 结果却变成了声明了一个函数
w1Widget w1();

// 函数声明无法使用大括号只当形参
// 结果只能为Widget 对象
Widget w2{};


在条款2时,我们就说过,如果使用auto来接收大括号初始化的变量,那么auto将被推导为std::initializer_list类型,所以使用大括号作为函数形参时,其类型是:std::initializer_list<T>。如果某个重载函数的形参为std::initializer_list<T>,则会匹配所有大括号的型别,具体会分以下几种情况进行处理:

  • 1、std::initializer_list<T>类型完全匹配,则直接调用该重载函数

  • 2、若实参需要提升,则先提升后匹配

  • 3、若实参需要窄化才能匹配,则编译报错

  • 3、如果实参完全不可能匹配到std::initializer_list<T>中的类型,则判断是否又满足该类型的其他重载函数

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

}
long l = 0;
double d = 1.0;
Widget w1{l,d};  //完全匹配,正确调用第二个构造函数int i = 0;
Widget w2{i,d};  //类型提升,将i提升为long类型后,匹配第二个long long ll = 0;
Widget w2{ll,d};  //需要窄化,编译器将报错string s = “123”;
Widget w2{s,d};  //从string不可能转换为long,将匹配第一个重载函数