1 各种初始化

  1. C++11中,可以使用()、= 或 { } 来指定初始化值。
int x(0); 		
int y = 0; 		
int z{ 0 }; 		
int z = { 0 }; // 等价于 int z{ 0 }; 
  1. 对于像 int 这样的内置类型可能无所谓,但对于用户定义的类型,区分初始化和赋值是很重要的,因为涉及到不同的函数调用:
Widget w1; 		// 调用默认构造函数
Widget w2 = w1; 	// 不是赋值,调用拷贝构造函数
w1 = w2; 		// 是赋值; 调用拷贝赋值运算符(copy operator=)
  1. C++11之前,不能直接使用一组特定的值(例如,1、3 和 5)来创建一个 STL 容器。从概念上来说是“统一初始化”;从语法结构上来说是“大括号初始化”。
std::vector<int> v{ 1, 3, 5 }; // v 的初始化内容为:1, 3, 5
  1. C++11 中,可以使用{ }, = 对非静态数据成员进行初始化,但( ) 不行:
class Widget {private:
    int x{ 0 }; 	// 正确, x的默认值为0
    int y = 0; 	// 同样正确
    int z(0); 		// 错误!
};
  1. 不可复制的对象(例如,std::atomics)可以使用{ }或()进行初始化,但不能使用 = :
std::atomic<int> ai1{ 0 }; 	// 正确
std::atomic<int> ai2(0); 	// 正确
std::atomic<int> ai3 = 0; 	// 错误!
  1. { }初始化禁止内置类型之间的隐式窄化转换。
double x, y, z;int sum1{ x + y + z }; 	// 错误! 双精度数的和可能无法表示为整数
int sum2(x + y + z); 		// 可以 (表达式的值被截断为整数)
int sum3 = x + y + z; 	// 同上
  1. { } 初始化不受 C++中令人烦恼的解析问题(任何可以被解析为声明的东西都必须被解释为声明)的影响。
Widget w1(10); 	// 调用 Widget 类的构造函数,并传入参数10
Widget w2(); 		// 令人烦恼的解析!声明了名为 w2 的函数,它返回一个 Widget!
Widget w3{}; 		// 无参数,调用构造函数
  1. { } 初始化的缺点是随之而来的有时令人惊讶的行为。
class Widget {
public:// 没有定义std::initializer_list 参数版本的ctor
    Widget(int i, bool b); 	
    Widget(int i, double d);};
Widget w1(10, true); 		// 调用第一个 ctor
Widget w2{10, true}; 		// 还是调用第一个 ctor
Widget w3(10, 5.0); 		// 调用第二个 ctor
Widget w4{10, 5.0}; 		// 还是调用第二个 ctor
//在构造函数调用中,只要不涉及 std::initializer_list 参数,()和{ }的含义是相同的。
  1. 如果存在构造函数声明了类型为 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> il); // 新加入};

Widget w1(10, true); 	// 调用第一个ctor
Widget w2{10, true}; 	// 调用initializer_list ctor(10和true转换为long double)
Widget w3(10, 5.0); 	// 调用第二个ctor
Widget w4{10, 5.0}; 	// 调用initializer_list ctor(10和5.0转换为long double)
  1. std::initializer_list 构造函数还可能劫持拷贝和移动构造函数。
class Widget {
public:
    Widget(int i, bool b); 	// 和以前一样
    Widget(int i, double d); 	// 和以前一样
    Widget(std::initializer_list<long double> il); // 和以前一样
    operator float() const; 	// 转换为float};
Widget w5(w4); // 调用 copy ctor
Widget w6{w4}; // 调用 std::initializer_list ctor
		// (w4 转换为 float, float 转换为 long double)
Widget w7(std::move(w4)); // 调用 move ctor
Widget w8{std::move(w4)}; // 调用 std::initializer_list ctor(原因和w6一样)
  1. 即使匹配出来的 std::initializer_list 构造函数无法被调用,std::initializer_list 的构造函数也会占上风。
 class Widget {
public:
    Widget(int i, bool b); 		// 和以前一样
    Widget(int i, double d); 		// 和以前一样
    //元素类型现在为bool
    Widget(std::initializer_list<bool> il); 
}; 
Widget w{10, 5.0}; // 错误! 需要窄化转换
//注意:类里不存在隐式类型转换
  1. 只有当没有办法将{ }中的参数类型转换为 std::initializer_list 中的类型时,编译器才会回退到正常的重载解析。
class Widget {
public:
    Widget(int i, bool b); 	// 和以前一样
    Widget(int i, double d); 	// 和以前一样
    // std::initializer_list 元素类型现在是 std::string
    Widget(std::initializer_list<std::string> il);// 不存在隐式的类型转换
}; 
Widget w1(10, true); 		// 调用第一个 ctor
Widget w2{10, true}; 		// 调用第一个 ctor
Widget w3(10, 5.0); 		// 调用第二个 ctor
Widget w4{10, 5.0}; 		// 调用第二个 ctor
  1. 假设使用空{ }来构造一个支持默认构造且还支持std::initializer_list构造的对象。那么空括{ }意味着什么?
class Widget {
public:
    Widget(); // default ctor
    Widget(std::initializer_list<int> il); // std::initializer_list ctor// 不存在隐式转换
}; 

// 空{ }意味着没有参数,而不是空的 std::initializer_list
Widget w1; 	// default ctor
Widget w2{}; 	// default ctor
Widget w3(); 	// 难以理解的语法解析! 声明了一个函数!

// 将空{ }放在()或{ }内,表面传递一个空的 std::initializer_list
Widget w4({}); // 使用空列表,调用 std::initializer_list ctor
Widget w5{ {} }; // 同上
  1. std::vector有一个非std::initializer_list的构造函数,允许指定容器的初始大小以及初始值,但它也有一个接受std::initializer_list的构造函数,允许指定容器中的初始值列表。
std::vector<int> v1(10, 20); // 使用 non-std::initializer_list ctor
std::vector<int> v2{10, 20}; // 使用 std::initializer_list ctor
  1. 对于模板的作者来说,在创建对象时选择使用()还是{ }可能会很困难,因为通常无法确定谁更好。
template<typename T, 	// 需要创建的对象的类型
    typename... Ts> 	// 参数列表的类型
    void doSomeWork(Ts&&... params)
{
    //使用params创建T类型的局部对象,下列方案二选一
T localObject(std::forward<Ts>(params)...); // 方案1
T localObject{std::forward<Ts>(params)...}; // 方案2}

doSomeWork<std::vector<int>>(10, 20); 	// 方案1:10个元素,值都为20
						// 方案2:2个元素,值为10和20

2 要点速记

  1. { } 初始化是最广泛可用的初始化语法,它防止窄化转换,并且不受令人烦恼的解析问题的影响。
  2. 在构造函数重载解析期间,如果可能的话,{ } 初始化表达式将与 std::initializer_list 参数匹配,即使其他构造函数提供了看似更好的匹配。
  3. ()和 { } 之间的选择可能产生重大影响,例如使用两个参数创建 std::vector<数值类型>。
  4. 在模板内部创建对象时,选择使用()还是 { } 可能很棘手。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐