MyClass a1 {a}; // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);
为什么?
std::map<std::string, std::vector<std::string>>::const_iterator
想和你谈谈。
using MyContainer = std::map<std::string, std::vector<std::string>>;
更好(尤其是您可以对其进行模板化!)
基本上是从 Bjarne Stroustrup 的“The C++ Programming Language 4th Edition”复制和粘贴:
列表初始化不允许缩小 (§iso.8.5.4)。那是:
一个整数不能转换为另一个不能保存其值的整数。例如,允许从 char 到 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)
}
only = 优先于 {} 的情况是使用 auto
关键字来获取由初始化程序确定的类型。
例子:
auto z1 {99}; // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99; // z3 is an int
结论
除非您有充分的理由不这样做,否则首选 {} 初始化而不是替代方法。
关于使用列表初始化的优点已经有了很好的答案,但是我个人的经验法则是尽可能不要使用花括号,而是让它依赖于概念含义:
如果我正在创建的对象在概念上包含我在构造函数中传递的值(例如容器、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{} {} };
<- 不尝试创建一个未初始化的引用(就像 ()
会做的那样),而是将它绑定到一个临时整数对象,然后让它悬空。 GCC 即使使用 -Wall
也不会对第二个示例发出警告。
使用大括号初始化的原因有很多,但您应该知道,initializer_list<>
构造函数优于其他构造函数,默认构造函数除外。这会导致构造函数和模板出现问题,其中类型 T
构造函数可以是初始化列表或普通的旧 ctor。
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
规则甚至存在 - 它只是给语言增加了混乱和混乱。如果您想要 std::initializer_list
构造函数,那么执行 Foo{{a}}
有什么问题?这似乎比让 std::initializer_list
优先于所有其他重载更容易理解。
Foo{{a}}
对我来说遵循一些逻辑远远超过Foo{a}
变成初始化器列表优先级(用户可能会认为嗯...)
std::initializer_list<Foo>
构造函数,但它会在某个时候被添加到 Foo
类以扩展其接口?然后 Foo
类的用户搞砸了。
initializer_list<>
),它并没有真正限定 谁 说它是首选的,然后继续提到它不 首选的一个很好的案例.我错过了大约 30 位其他人(截至 2016 年 4 月 21 日)发现有帮助的地方吗?
只要您不使用 -Wno-narrowing 进行构建,就像 Google 在 Chromium 中所做的那样,它只会更安全。如果你这样做了,那就不太安全了。如果没有这个标志,唯一的不安全情况将由 C++20 修复。
注意:A)大括号更安全,因为它们不允许变窄。 B)花括号不太安全,因为它们可以绕过私有或删除的构造函数,并隐式调用显式标记的构造函数。
这两个组合意味着如果里面是原始常量,它们会更安全,但如果它们是对象,则安全性会降低(尽管在 C++20 中已修复)
clang++ -std=c++14
告诉我main.cpp:22:7: error: calling a private constructor of class 'Foo'
。就隐式调用显式构造函数而言,该参数甚至没有意义。这是一个隐式构造函数调用:foo_instance = false;
。 false 通过调用匹配的构造函数被隐式转换为 Foo 。如果使用大括号,则显式调用构造函数。关键是你不能在不提及类型名称的情况下使用花括号进行这样的赋值。
更新 (2022-02-11):请注意,与最初发布的主题(下)相比,有更多关于该主题的最新意见反对 {} 初始化程序的偏好,例如 Arthur Dwyer 在他关于 {1 }。
原答案:
阅读Herb Sutter's (updated) GotW #1。这详细解释了这些之间的区别,以及更多选项,以及与区分不同选项的行为相关的几个陷阱。
第 4 节的要点/复制:
什么时候应该使用 ( ) 与 { } 语法来初始化对象?为什么?下面是简单的指导方针: 指导方针:优先使用带有 { } 的初始化,例如向量 v = { 1, 2, 3, 4 };或 auto v = vector{ 1, 2, 3, 4 };,因为它更一致、更正确,并且完全避免了了解旧式陷阱。在您希望只看到 = 符号的单参数情况下,例如 int i = 42;和自动 x = 任何东西;省略括号很好。 ......这涵盖了绝大多数情况。只有一个主要的例外:……在极少数情况下,例如向量 v(10,20);或 auto v = vector(10,20);,使用 ( ) 初始化显式调用构造函数,否则该构造函数会被 initializer_list 构造函数隐藏。然而,这通常应该是“罕见的”的原因是因为默认和复制构造已经很特殊并且可以与 { } 一起工作,并且好的类设计现在大多避免用户定义构造函数的求助于()情况,因为这个最终设计指南: 指南:当你设计一个类时,避免提供一个用 initializer_list 构造函数重载的构造函数,这样用户就不需要使用 ( ) 来访问这样一个隐藏的构造函数。
另请参阅有关该主题的核心指南:ES.23: Prefer the {}-initializer syntax。
()
可以被解析为函数声明。您可以说T t(x,y,z);
而不是T t()
,这是令人困惑和不一致的。有时,你确定的x
,你甚至不能说T t(x);
。std::initializer_list
的 ctor 时,大括号初始化变得一团糟。 RedXIII 提到了这个问题(并且只是忽略了它),而您完全忽略了它。A(5,4)
和A{5,4}
可以调用完全不同的函数,这一点很重要。它甚至可能导致看起来不直观的调用。说默认情况下您应该更喜欢{}
会导致人们误解发生了什么。不过,这不是你的错。我个人认为这是一个经过深思熟虑的功能。auto var{ 5 }
,它将被推断为int
而不是std::initializer_list<int>
。