初始化时统一初始化还是直接初始化?
Uniform- or direct-initialization when initializing?
假设我有一个存储类型为 T
的对象的模板。我想传递构造函数参数以初始化数据成员。我应该使用统一初始化还是带非花括号的直接初始化?:
template<typename T>
struct X
{
template<typename... Args>
X(Args&&... args)
: t(std::forward<Args>(args)...) // ?
/* or */ : t{std::forward<Args>(args)...} // ?
private:
T t;
};
如果我要存储的对象是 std::vector
并且我选择花括号样式(统一初始化),那么我传递的参数将被转发到 vector::vector(std::initializer_list<T>)
构造函数,它可能是也可能不是我想要的。
另一方面,如果我使用非花括号样式,我将失去通过其 std::initializer_list
构造函数向向量添加元素的能力。
当我不知道我正在存储的对象和将传入的参数时,我应该使用什么形式的初始化?
需要明确的是,具有多个构造函数的类型会出现歧义,其中一个构造函数采用 std::initializer_list
,而另一个其参数(使用大括号初始化时)可能被解释为 std::initializer_list
的类型编译器。例如, std::vector<int>
:
就是这种情况
template<typename T>
struct X1
{
template<typename... Args>
X1(Args&&... args)
: t(std::forward<Args>(args)...) {}
T t;
};
template<typename T>
struct X2
{
template<typename... Args>
X2(Args&&... args)
: t{std::forward<Args>(args)...} {}
T t;
};
int main() {
auto x1 = X1<std::vector<int>> { 42, 2 };
auto x2 = X2<std::vector<int>> { 42, 2 };
std::cout << "size of X1.t : " << x1.t.size()
<< "\nsize of X2.t : " << x2.t.size();
}
(请注意,唯一的区别是 X2
成员初始化列表中的大括号而不是 X1
成员初始化列表中的括号)
Output :
size of X1.t : 42
size of X2.t : 2
标准库作者在编写 std::make_unique
、std::make_shared
或 std::optional<>
等实用程序模板时遇到了这个真正的问题(应该完美转发任何类型):哪种初始化形式是首选?这取决于客户端代码。
没有好的答案,他们通常会加上括号(最好记录选择,以便调用者知道会发生什么)。惯用的现代 c++11 更喜欢在任何地方进行大括号初始化(它避免缩小转换,避免 c++ 最令人烦恼的解析等)
消除歧义的一个潜在解决方法是使用 命名标签 ,在 Andrzej's C++ blog 的这篇精彩文章中进行了广泛讨论:
namespace std{
constexpr struct with_size_t{} with_size{};
constexpr struct with_value_t{} with_value{};
constexpr struct with_capacity_t{} with_capacity{};
}
// These contructors do not exist.
std::vector<int> v1(std::with_size, 10, std::with_value, 6);
std::vector<int> v2{std::with_size, 10, std::with_value, 6};
这是冗长的,仅当您可以修改不明确的类型时才适用(例如,公开采用 std::initializer_list
的构造函数的类型和其他参数列表可能转换为 std::initializer list
的构造函数)
与任何初始化一样,
当对象包含一个值或多个正在分段初始化的值时使用大括号。这包括汇总 类 和数字。
大括号看起来更像是项目列表。
根据参数计算对象的初始状态时使用括号。
括号看起来更像是一系列函数参数。
这个一般规则包括像 std::vector<int>
这样的容器的情况,它可以用 N 个数字 (std::vector<int>(4,5)
) 或一对数字 (std::vector<int>{4,5}
) 的副本来初始化。
顺便说一下,由于“统一”初始化并不是真正的包罗万象,因此不鼓励使用该术语。正式名称是 brace-initialization.
假设我有一个存储类型为 T
的对象的模板。我想传递构造函数参数以初始化数据成员。我应该使用统一初始化还是带非花括号的直接初始化?:
template<typename T>
struct X
{
template<typename... Args>
X(Args&&... args)
: t(std::forward<Args>(args)...) // ?
/* or */ : t{std::forward<Args>(args)...} // ?
private:
T t;
};
如果我要存储的对象是 std::vector
并且我选择花括号样式(统一初始化),那么我传递的参数将被转发到 vector::vector(std::initializer_list<T>)
构造函数,它可能是也可能不是我想要的。
另一方面,如果我使用非花括号样式,我将失去通过其 std::initializer_list
构造函数向向量添加元素的能力。
当我不知道我正在存储的对象和将传入的参数时,我应该使用什么形式的初始化?
需要明确的是,具有多个构造函数的类型会出现歧义,其中一个构造函数采用 std::initializer_list
,而另一个其参数(使用大括号初始化时)可能被解释为 std::initializer_list
的类型编译器。例如, std::vector<int>
:
template<typename T>
struct X1
{
template<typename... Args>
X1(Args&&... args)
: t(std::forward<Args>(args)...) {}
T t;
};
template<typename T>
struct X2
{
template<typename... Args>
X2(Args&&... args)
: t{std::forward<Args>(args)...} {}
T t;
};
int main() {
auto x1 = X1<std::vector<int>> { 42, 2 };
auto x2 = X2<std::vector<int>> { 42, 2 };
std::cout << "size of X1.t : " << x1.t.size()
<< "\nsize of X2.t : " << x2.t.size();
}
(请注意,唯一的区别是 X2
成员初始化列表中的大括号而不是 X1
成员初始化列表中的括号)
Output :
size of X1.t : 42
size of X2.t : 2
标准库作者在编写 std::make_unique
、std::make_shared
或 std::optional<>
等实用程序模板时遇到了这个真正的问题(应该完美转发任何类型):哪种初始化形式是首选?这取决于客户端代码。
没有好的答案,他们通常会加上括号(最好记录选择,以便调用者知道会发生什么)。惯用的现代 c++11 更喜欢在任何地方进行大括号初始化(它避免缩小转换,避免 c++ 最令人烦恼的解析等)
消除歧义的一个潜在解决方法是使用 命名标签 ,在 Andrzej's C++ blog 的这篇精彩文章中进行了广泛讨论:
namespace std{
constexpr struct with_size_t{} with_size{};
constexpr struct with_value_t{} with_value{};
constexpr struct with_capacity_t{} with_capacity{};
}
// These contructors do not exist.
std::vector<int> v1(std::with_size, 10, std::with_value, 6);
std::vector<int> v2{std::with_size, 10, std::with_value, 6};
这是冗长的,仅当您可以修改不明确的类型时才适用(例如,公开采用 std::initializer_list
的构造函数的类型和其他参数列表可能转换为 std::initializer list
的构造函数)
与任何初始化一样,
当对象包含一个值或多个正在分段初始化的值时使用大括号。这包括汇总 类 和数字。
大括号看起来更像是项目列表。
根据参数计算对象的初始状态时使用括号。
括号看起来更像是一系列函数参数。
这个一般规则包括像 std::vector<int>
这样的容器的情况,它可以用 N 个数字 (std::vector<int>(4,5)
) 或一对数字 (std::vector<int>{4,5}
) 的副本来初始化。
顺便说一下,由于“统一”初始化并不是真正的包罗万象,因此不鼓励使用该术语。正式名称是 brace-initialization.