【Effective Modern C++】第三章 转向现代C++:7. 在创建对象时注意区分()和{}

初始化的基本方式

指定初始化的方式包括使用小括号、使用等号,或是使用大括号:

c++ 复制代码
int x(0);
int y = 0;
int z{0};

很多情况下使用一个等号和一对大括号也是可以的:(处理方式等同于只有大括号)

c++ 复制代码
int z = {0};

对于用户定义的类型,等号的初始化方式实际并没有发生赋值:

c++ 复制代码
Widget w1; // 调用的是默认构造函数
Widget w2 = w1; // 井非赋值,调用的是复制构造函数
w1 = w2;  // 并非赋值.调用的是复制赋值运算符

C++11引入了统一初始化:使用大括号进行初始化。

  1. 使用大括号来制定容器的初始内容:
c++ 复制代码
std: :vector<int> v{ 1, 3, 5 }; // v 的初始内容为1、3、5
  1. 为非静态成员指定默认初始化值(也可以使用"=",但是()不行):
c++ 复制代码
class Widget 
{ 
	... 
private: 
	int x{ 0 }; // 可行,x 的默认值为 0 
	int y = 0; // 也可行 
	int z(0); // 不可行! 
};

不可复制对象(如std::atomic类型的对象)可以使用大括号和小括号来进行复制,但是不能使用"="。

由上可以看出,只有大括号初始化适用于所有场景

大括号初始化的特性

  1. 禁止内建类型之间进行隐式窄化类型转换 。如果大括号内的表达式无法保证能够采用进行初始化的对象来表达,则代码不能通过编译。
    如:
c++ 复制代码
double x, y, z; 
int sum1{ x + y + z }; // 错误!double类型之和可能无法用int表达
  1. 对于C++最令人苦恼的解析语法 (MVP:任何能够解析为声明的都要解析为声明。最苦恼------程序员本来想要以默认方式构造一个对象,却不小心声明了一个函数)免疫。
    如:
c++ 复制代码
Widget w1(); // 解析为函数声明(MVP问题) 
Widget w2{}; // 明确为对象初始化,无歧义

存在的缺陷

1. auto变量的异常推导

auto结合{}初始化时,变量类型会被推导为std::initializer_list(而非直觉类型):

c++ 复制代码
auto a = 10;          // 推导为int(符合直觉)
auto b{10};           // C++11/14:std::initializer_list<int>;C++17后:int
auto c = {10, 20};    // 始终推导为std::initializer_list<int>

2. 构造函数重载决议的优先级规则

  • std::initializer_list形参时(){}初始化结果一致:
c++ 复制代码
    class Widget { 
    public:  
        Widget(int i, bool b);      // 无std::initializer_list形参
        Widget(int i, double d);    
        ...
    };
    Widget w1(10, true);            // 调用第一个构造函数
    Widget w2{10, true};            // 调用第一个构造函数
    Widget w3(10, 5.0);             // 调用第二个构造函数
    Widget w4{10, 5.0};             // 调用第二个构造函数
  • std::initializer_list形参时{}强制优先匹配该构造函数(即使普通构造更匹配):
c++ 复制代码
    class Widget { 
    public:  
        Widget(int i, bool b);      
        Widget(int i, double d);    
        Widget(std::initializer_list<long double> il); // 新增
        ...
    }; 
    Widget w1(10, true);    // 调用第一个构造函数(()不受影响)
    Widget w2{10, true};    // 调用std::initializer_list构造(10/true转long double)
    Widget w3(10, 5.0);     // 调用第二个构造函数(()不受影响)
    Widget w4{10, 5.0};     // 调用std::initializer_list构造(10/5.0转long double)
  • 优先级极端性 :即便std::initializer_list构造无法调用,编译器仍会优先尝试(失败才回退普通决议):
c++ 复制代码
    class Widget { 
    public: 
        Widget(int i, bool b);      
        Widget(int i, double d);    
        Widget(std::initializer_list<bool> il); // 元素类型为bool
        ...
    };
    Widget w{10, 5.0};              // 错误!10/5.0转bool属于窄化转换,{}禁止
  • {}的边界规则 :空{}表示 "无实参",优先调用默认构造(而非空std::initializer_list);需传空列表时需嵌套括号:
c++ 复制代码
    class Widget { 
    public:  
        Widget();                                   // 默认构造
        Widget(std::initializer_list<int> il);      // std::initializer_list构造
        ...
    };
    Widget w1;                      // 调用默认构造
    Widget w2{};                    // 调用默认构造
    Widget w3();                    // MVP!声明函数
    Widget w4({});                  // 调用std::initializer_list构造(空列表)
    Widget w5{{}};                  // 同上

3. std::vector(){}初始化结果差异巨大

c++ 复制代码
std::vector<int> v1(10, 20);    // 非std::initializer_list构造:10个元素,值均为20
std::vector<int> v2{10, 20};    // std::initializer_list构造:2个元素,值为10、20

总结

  • 大括号初始化可以应用的语境最为宽泛,可以阻止隐式窄化类型转换,还对最令人苦恼之解析语法免疫。
  • 在构造函数重载决议期间,只要有任何可能,大括号初始化物就会与带有std::initializer_list类型的形参相匹配,即使其他重载版本有着貌似更加匹配的形参表。
  • 对于数值类型的std::vector来说使用大括号初始化和小括号初始化会造成巨大的不同
  • 在模板内容进行对象创建时,使用小括号还是大括号会成为一大挑战。

原著在线阅读地址

相关推荐
Bruce_kaizy2 小时前
c++ dfs搜索算法——剪枝
c++·深度优先·剪枝
CSDN_RTKLIB2 小时前
【std::string】find函数
c++·stl
十五年专注C++开发2 小时前
浅谈CPU中的SIMD
c++·cpu·代码优化·simd
Yu_Lijing3 小时前
基于C++的《Head First设计模式》笔记——状态模式
c++·笔记·设计模式
顶点多余3 小时前
静态链接 vs 动态链接,静态库 vs 动态库
linux·c++·算法
AI视觉网奇3 小时前
ue5 开发 web socket server 实战2026
c++·学习·ue5
王老师青少年编程3 小时前
2024年3月GESP真题及题解(C++八级): 接竹竿
c++·题解·真题·gesp·csp·八级·接竹竿
偷星星的贼114 小时前
C++中的访问者模式实战
开发语言·c++·算法
雾岛听蓝4 小时前
红黑树深度解析:设计原理与实现逻辑
c++