何时使用大括号初始化程序?
在C ++ 11中,我们有了初始化类的新语法,这给我们提供了大量的如何初始化variables的可能性。
{ // Example 1 int b(1); int a{1}; int c = 1; int d = {1}; } { // Example 2 std::complex<double> b(3,4); std::complex<double> a{3,4}; std::complex<double> c = {3,4}; auto d = std::complex<double>(3,4); auto e = std::complex<double>{3,4}; } { // Example 3 std::string a(3,'x'); std::string b{3,'x'}; // oops } { // Example 4 std::function<int(int,int)> a(std::plus<int>()); std::function<int(int,int)> b{std::plus<int>()}; } { // Example 5 std::unique_ptr<int> a(new int(5)); std::unique_ptr<int> b{new int(5)}; } { // Example 6 std::locale::global(std::locale("")); // copied from 22.4.8.3 std::locale::global(std::locale{""}); } { // Example 7 std::default_random_engine a {}; // Stroustrup's FAQ std::default_random_engine b; } { // Example 8 duration<long> a = 5; // Stroustrup's FAQ too duration<long> b(5); duration<long> c {5}; }
对于我声明的每个variables,我必须考虑使用哪种初始化语法,这会降低我的编码速度。 我敢肯定,这不是引入大括号的意图。
当涉及到模板代码时,更改语法可能会导致不同的含义,所以正确的方法至关重要。
我想知道是否有一个通用的指导方针,应该select哪种语法。
我认为以下可能是一个很好的指导方针:
-
如果您正在初始化的(单个)值旨在成为对象的确切值 ,请使用copy(
=
)初始化(因为在发生错误的情况下,您将永远不会意外地调用显式构造函数,这通常会解释提供的价值不同)。 在复制初始化不可用的地方,看看括号初始化是否有正确的语义,如果是的话,使用它; 否则使用圆括号初始化(如果这还不可用,无论如何,你是运气不好)。 -
如果正在初始化的值是要存储在对象中的值的列表(如向量/数组的元素,或复数的实数/虚数部分),请使用花括号初始化(如果可用)。
-
如果正在初始化的值不是要存储的值,而是描述对象的预期值/状态,请使用括号。 例子是
vector
的大小参数或fstream
的文件名参数。
我很确定,永远不会有一个通用的指导方针。 我的方法是总是使用花括号来记住这一点
- 初始化列表构造函数优先于其他构造函数
- 所有标准库容器和std :: basic_string都有初始化列表构造函数。
- 大括号初始化不允许缩小转换。
所以圆括号和大括号是不可互换的。 但是知道它们的不同之处,可以让我在大多数情况下(在某些情况下,当前不能成为编译器错误的情况下)使用大于圆括号的初始化。
通用代码(即模板)之外,你可以(和我)在任何地方使用大括号 。 其中一个优点是它可以在任何地方工作,例如即使是在课堂上的初始化:
struct foo { // Ok std::string a = { "foo" }; // Also ok std::string b { "bar" }; // Not possible std::string c("qux"); // For completeness this is possible std::string d = "baz"; };
或者用于函数参数:
void foo(std::pair<int, double*>); foo({ 42, nullptr }); // Not possible with parentheses without spelling out the type: foo(std::pair<int, double*>(42, nullptr));
对于variables我不太在意T t = { init };
或者T t { init };
样式,我发现区别是轻微的,最坏的情况下,只会导致有用的编译器消息滥用explicit
构造函数。
对于接受std::initializer_list
types,显然有时需要使用非std::initializer_list
构造函数(经典的例子是std::vector<int> twenty_answers(20, 42);
)。 那么不要使用大括号。
谈到通用代码(即模板),最后一段应该提出一些警告。 考虑以下几点:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
然后auto p = make_unique<std::vector<T>>(20, T {});
如果T
是例如int
,则创build大小为2的向量;如果T
是std::string
,则创build大小为20的向量。 一个非常明显的迹象表明,在这里发生了一些非常错误的事情,那就是没有什么特性可以在这里保存(比如用SFINAE): std::is_constructible
是直接初始化,而我们使用的是大括号初始化当且仅当没有构造函数采用std::initializer_list
干涉时才推迟直接std::initializer_list
。 同样, std::is_convertible
没有任何帮助。
我已经调查过,如果事实上是可能的,可以修复这个问题,但我并不太乐观。 无论如何,我认为我们不会错过太多的东西,我认为make_unique<T>(foo, bar)
结果相当于T(foo, bar)
结构非常直观。 特别是考虑到make_unique<T>({ foo, bar })
是非常不相似的,只有当foo
和bar
具有相同的types时才有意义。
因此, 对于generics代码,我只使用花括号来进行值初始化 (例如T t {};
或T t = {};
),这非常方便,我认为优于C ++ 03的方式T t = T();
。 否则,它可能是直接初始化语法 (即T t(a0, a1, a2);
),或者有时是默认构造( T t; stream >> t;
是我使用它的唯一情况)。
这并不意味着所有的大括号都是坏的,考虑以前的例子修复:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
即使实际types依赖于模板参数T
,仍然使用大括号来构造std::unique_ptr<T>
。