在C++11中,我们有了初始化类的新语法,这为我们提供了如何初始化变量的大量可能性。
{ // 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};
}
对于我声明的每个变量,我都必须考虑应该使用哪种初始化语法,这会减慢我的编码速度。我确信这不是引入花括号的意图。
当涉及到模板代码时,更改语法可能会导致不同的含义,因此采用正确的方式至关重要。
我想知道是否有一个通用的指导方针,人们应该选择哪种语法。
I认为以下可能是一个很好的指导方针:
-
如果要初始化的(单个)值是对象的精确值,请使用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));
对于我不太注意T t = { init };
或T t { init };
样式之间的变量,我发现差异很小,最坏的情况下只会导致编译器发出关于滥用explicit
构造函数的有用消息。
对于接受std::initializer_list
的类型,尽管显然有时需要非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)... } }; }
然后,如果T
是例如int
,则auto p = make_unique<std::vector<T>>(20, T {});
创建大小为2的向量,或者如果T
是std::string
,则创建大小为20的向量。一个非常明显的迹象表明,这里发生了一些非常错误的事情,那就是没有特征可以拯救你(例如,使用SFINAE):std::is_constructible
是直接初始化的,而我们使用的是大括号初始化,它推迟于直接初始化,当且仅当没有构造函数接受std::initializer_list
的干扰。同样,std::is_convertible
也无济于事。
我已经研究过是否真的有可能手动滚动一个可以解决这个问题的特性,但我对此并不过于乐观。在任何情况下,我不认为我们会错过太多,我认为make_unique<T>(foo, bar)
导致相当于T(foo, bar)
的构造的事实是非常直观的;特别是考虑到CCD_ 20非常不同并且只有当CCD_ 21和CCD_。
因此对于泛型代码,我只使用大括号进行值初始化(例如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)...) }; }
这仍然使用大括号来构造std::unique_ptr<T>
,即使实际类型取决于模板参数T
。