关于C++11的统一初始化语法示例详解

2020-01-06 17:23:08刘景俊

除了上面的优势之外,列表初始化语法还可以杜绝C++重构造语法的阴暗面。C++秉承的一个观念就是任何可以被解释为声明语法的语句都会被解释为声明语句,这会导致调用默认构造函数创建对象的时候被用错。


Widget w(); // 被解释为函数声明
Widget w{}; // OK

另外一种情况就是在容器使用的时候,也比较容易产生混淆的语义,这个时候使用列表初始初始化语法可以表明我们提供的列表是实际的元素。因为容器类的构造函数具有使用std::initializer_list作为重载的版本,所以如果要显式调用其某个版本的构造函数,就需要使用()来规避std::initializer_list的版本,称之为ctor-resort。


vector<int> v1{99};   // 一个元素,值为99
vector<int> v2(99);   // 实际是调用构造函数,共99个元素,默认值都是0
vector<string> v2("hello");  // Error,无匹配的构造函数

二、统一初始化器的阴暗面

使用列表初始化语法在绝大多数情况都能胜任,而且工作的很好,但是一旦同std::initializer_list结合起来,它的使用就会让人感觉混淆不清。在auto进行类型自动推导的时候,{}会默认被推导为std::initializer_list,如果这种结果不是你想要的,就需要进行规避以使用其他方式进行初始化操作。


auto z1 {99}; // initializer_list<int>
auto z2 = 99; // int

如果你认为避免上面那个坑就结束了,呵呵……统一初始化器最大的麻烦还在于其和构造函数的结合。如果某个类的构造函数,其提供了一个接收std::initializer_list作为参数类型的重载版本,那么使用统一初始化句法进行构造对象的时候,编译器将会强烈优先使用具有初始化列表的重载版本。

我们知道,以std::initializer_list作为形参的话,其实参列表中的元素不要求和T完全匹配,而只需要能转换成T即可,此时只要转换后满足要求,编译器都会优先使用std::initializer_list作为形参的重载版本,即使其他重载的构造函数具有更优的匹配。在转换的过程中,如果类型提升满足要求则会正常调用;如果发生了窄化转换,则调用会失败报错;只有诸如字符串和数字这类无法转换的类型相互重载时候,重载机制才可能正常工作。


struct Widget {
 Widget(int i, bool b) { cout << "1" << endl; }
 Widget(int i, double d) { cout << "2" << endl; }
 Widget(std::initializer_list<bool> il) { cout << "3" << endl; }
};
Widget w1{1, true}; // 3
Widget w2{9, true}; // Error

还有一个极端情况,如果一个自定义类既有默认构造函数,也有