提问人:Oleksiy 提问时间:8/14/2013 最后编辑:HolyBlackCatOleksiy 更新时间:7/21/2023 访问量:297303
列表初始化(使用大括号)的优点是什么?
What are the advantages of list initialization (using curly braces)?
问:
MyClass a1 {a}; // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);
为什么?
答:
基本上是从Bjarne Stroustrup的“C++编程语言第4版”中复制和粘贴:
列表初始化不允许缩小范围 (§iso.8.5.4)。那是:
- 一个整数不能转换为另一个不能保持其值的整数。例如,char 允许 to int,但不允许 int 到 char。
- 浮点值不能转换为另一个不能保存其浮点类型的浮点值 价值。例如,允许将浮点数转换为双精度,但不允许将双精度转换为浮点数。
- 浮点值不能转换为整数类型。
- 整数值不能转换为浮点类型。
例:
void fun(double val, int val2) {
int x2 = val; // if val == 7.9, x2 becomes 7 (bad)
char c2 = val2; // if val2 == 1025, c2 becomes 1 (bad)
int x3 {val}; // error: possible truncation (good)
char c3 {val2}; // error: possible narrowing (good)
char c4 {24}; // OK: 24 can be represented exactly as a char (good)
char c5 {264}; // error (assuming 8-bit chars): 264 cannot be
// represented as a char (good)
int x4 {2.0}; // error: no double to int value conversion (good)
}
= 优于 {} 的唯一情况是使用 keyword 获取初始值设定项确定的类型。auto
例:
auto z1 {99}; // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99; // z3 is an int
结论
首选 {} 初始化而不是替代方法,除非您有充分的理由不这样做。
评论
()
T t(x,y,z);
T t()
x
T t(x);
std::initializer_list
A(5,4)
A{5,4}
{}
auto var{ 5 }
int
std::initializer_list<int>
使用大括号初始化的原因有很多,但您应该知道,initializer_list<>
构造函数比其他构造函数更受欢迎,默认构造函数除外。这会导致构造函数和模板出现问题,其中类型构造函数可以是初始值设定项列表,也可以是普通的旧 ctor。T
struct Foo {
Foo() {}
Foo(std::initializer_list<Foo>) {
std::cout << "initializer list" << std::endl;
}
Foo(const Foo&) {
std::cout << "copy ctor" << std::endl;
}
};
int main() {
Foo a;
Foo b(a); // copy ctor
Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}
假设您没有遇到此类类,则没有理由不使用初始化程序列表。
评论
{ ... }
initializer_list
std::initializer_list
Foo{{a}}
std::initializer_list
std::initializer_list
Foo{{a}}
Foo{a}
std::initializer_list<Foo>
Foo
Foo
initializer_list<>
关于使用列表初始化的优势,已经有很好的答案,但是我个人的经验法则是尽可能不要使用大括号,而是让它依赖于概念含义:
- 如果我创建的对象在概念上包含我在构造函数中传递的值(例如容器、POD 结构、原子、智能指针等),那么我就使用大括号。
- 如果构造函数类似于普通的函数调用(它执行一些或多或少由参数参数化的复杂操作),那么我使用的是普通的函数调用语法。
- 对于默认初始化,我总是使用大括号。
首先,这样我始终可以确保对象被初始化,无论它是否是一个“真实”类,其默认构造函数无论如何都会被调用,还是内置/ POD类型。其次,在大多数情况下,它与第一条规则一致,因为默认初始化的对象通常表示“空”对象。
根据我的经验,默认情况下,与使用大括号相比,可以更一致地应用此规则集,但是当它们无法使用或与带有括号的“正常”函数调用语法具有不同的含义时,必须显式记住所有异常(调用不同的重载)。
例如,它与标准库类型非常吻合,例如:std::vector
vector<int> a{10, 20}; //Curly braces -> fills the vector with the arguments
vector<int> b(10, 20); //Parentheses -> uses arguments to parametrize some functionality,
vector<int> c(it1, it2); //like filling the vector with 10 integers or copying a range.
vector<int> d{}; //empty braces -> default constructs vector, which is equivalent
//to a vector that is filled with zero elements
评论
const int &b{}
struct A { const int &b; A():b{} {} };
()
-Wall
只要您不像 Google 在 Chromium 中那样构建,它就会更安全。如果你这样做,那么它就不太安全了。如果没有该标志,C++20 将修复唯一的不安全情况。-Wno-narrowing
注意:
- 大括号更安全,因为它们不允许变窄。
- 卷曲的 bracker 不太安全,因为它们可以绕过私有或已删除的构造函数,并隐式调用显式标记的构造函数。
这两者结合在一起意味着如果里面的东西是原始常量,它们更安全,但如果它们是对象,它们就不那么安全了(尽管在 C++20 中是固定的)
评论
clang++ -std=c++14
告诉我.就隐式调用显式构造函数而言,该参数甚至没有意义。这是一个隐式构造函数调用:。false 通过调用匹配构造函数隐式转换为 Foo。如果使用大括号,则显式调用构造函数。关键是你不能在不提及类型名称的情况下使用大括号进行这样的赋值。main.cpp:22:7: error: calling a private constructor of class 'Foo'
foo_instance = false;
更新(2022-02-11): 请注意,关于该主题的最新观点与最初发布的观点(如下)相反,这些观点反对 {} 初始值设定项的偏好,例如 Arthur Dwyer 在他的博客文章中 C++ 中初始化的骑士。
原文答案:
阅读 Herb Sutter 的(更新)GotW #1。 这详细解释了这些选项之间的区别,以及更多选项,以及与区分不同选项的行为相关的几个陷阱。
要点/复制自第 4 节:
何时应使用 ( ) 与 { } 语法来初始化对象?为什么? 以下是简单的指南:
指南:最好使用 { } 的初始化,例如 vector v = { 1, 2, 3, 4 };或者 auto v = vector{ 1, 2, 3, 4 };,因为 它更一致,更正确,并且避免了必须了解 完全是老式的陷阱。在您喜欢的单参数情况下 仅查看 = 符号,例如 int i = 42;和 auto x = 任何东西; 省略大括号是可以的。...
这涵盖了绝大多数情况。只有一个主要 例外:
...在极少数情况下,例如向量 v(10,20);或者 auto v = vector(10,20);,则使用带有 ( ) 的初始化来显式调用 以其他方式被initializer_list隐藏的构造函数 构造 函数。
但是,这通常应该是“罕见”的原因是因为默认 和副本结构已经很特殊了,并且可以很好地使用 { },并且 好的类设计现在大多避免了诉诸 ( ) 的情况 用户定义的构造函数,因为以下最终设计指南:
指南:在设计类时,请避免提供 模棱两可地重载一个initializer_list构造函数,以便 用户不需要使用 ( ) 来访问这样一个隐藏的构造函数。
另请参阅有关该主题的核心指南:ES.23:首选 {}-initializer 语法。
下一个:默认复制构造函数和赋值运算符
评论
std::map<std::string, std::vector<std::string>>::const_iterator
typedef std::map<std::string, std::vector<std::string>> MyContainer
auto
using MyContainer = std::map<std::string, std::vector<std::string>>;