条款7:区别使用()和{}创建对象
花括号初始化更通用,可防止窄化转换,避免最令人头疼的解析;但重载时易优先匹配 std::initializer_list 构造函数,需谨慎使用。
条款7:区别使用()和{}创建对象
条款7:区别使用()和{}创建对象
C++ 中的三种初始化方式
方式 | 示例 | 说明 |
---|---|---|
圆括号 () | int x(0); | 传统构造语法 |
等号 = | int x = 0; | 赋值初始化 |
花括号 {} | int x{0}; | C++11 引入的统一初始化方式,称作 brace initialization |
花括号初始化的优点
最统一的语法:可用于所有类型、所有上下文,包括成员变量初始化。
禁止变窄转换(narrowing conversion):
1
int x{2.5}; // 错误,防止精度丢失
规避 most vexing parse 问题:
1 2
Widget w1(); // 解析成函数声明(最令人头疼的解析) Widget w2{}; // 正确调用默认构造函数
most vexing parse
(最令人头疼的解析)是 C++ 语法中的一个 经典陷阱,指的是:本来想写对象的定义,结果却被 C++ 编译器解析成了函数声明。- 默认构造一个对象时,
{}
是最安全的方式。
支持直接列表初始化容器:
1
std::vector<int> v{1, 2, 3};
花括号初始化的陷阱
会优先匹配 std::initializer_list
构造函数
如果存在如下构造函数:
1
2
3
4
5
6
class Widget {
public:
Widget(int, bool);
Widget(std::initializer_list<long double>);
};
Widget w{10, true}; // 调用 initializer_list 构造函数,而非更匹配的 Widget(int, bool)
即使其他构造函数匹配更好,编译器也更偏向选择 std::initializer_list
版本。
会因变窄转换而导致编译失败
1
2
Widget(std::initializer_list<bool>);
Widget w{10, 5.0}; // 错误,因 bool 无法安全表示 10 和 5.0
花括号 vs 圆括号 初始化 std::vector<int>
1
2
std::vector<int> v1(10, 20); // 10个元素,值为20
std::vector<int> v2{10, 20}; // 2个元素,值为10, 20
两者行为完全不同,这类情形尤需小心选择。
类库作者的建议
- 如果提供了
std::initializer_list
构造函数,要确保不会意外劫持其他构造函数。 - 明确设计类时,花括号或圆括号初始化都应该能产生一致行为。
- 谨慎新增
std::initializer_list
构造函数,可能会破坏原有初始化逻辑。
模板中的挑战
在模板中,初始化方式影响结果:
1
2
3
4
5
template<typename T, typename... Args>
void doSomeWork(Args&&... args) {
T obj(std::forward<Args>(args)...); // 使用圆括号
// 或者 T obj{std::forward<Args>(args)...}; // 使用花括号
}
调用时:
1
doSomeWork<std::vector<int>>(10, 20);
- 圆括号:创建10个值为20的元素
- 花括号:创建两个元素,值为10 和 20
标准库的 make_shared
和 make_unique
统一使用圆括号避免歧义。
总结建议
- 默认使用花括号
{}
初始化,除非:- 需要调用非
initializer_list
构造函数 - 可能引起构造函数选择混乱
- 需要调用非
- 避免使用
=
初始化,容易混淆赋值与初始化语义 - 设计类时要意识到
initializer_list
的优先级问题 - 模板中初始化方式对行为影响极大,慎选
()
vs{}
本文由作者按照 CC BY 4.0 进行授权