为什么 C++ 列表初始化也考虑常规构造函数?
Why does C++ list initialization also take regular constructors into account?
在 C++ 中,当使用 initializer_list 语法初始化对象时,当没有其他列表初始化规则适用时,对象的常规构造函数也参与重载决策。
据我了解,以下代码调用 X::X(int)
class X { int a_; X(int a):a_(a) {} );
void foo() {
X bar{3};
}
但我不明白,为什么在 initializer_list 的上下文中也会考虑常规构造函数。我感觉现在很多程序员都写X{3}来调用构造函数而不是X(3)来调用构造函数。
我一点都不喜欢这种风格,因为它让我觉得这个对象没有常规的构造函数。
initializer_list 语法也可以用来调用常规构造函数的原因是什么?现在有理由比常规的构造函数调用更喜欢这种语法吗?
本质上是一团糟。对于 C++11,它试图创建一种统一的方法来初始化对象,而不是其他必要的多种方法:
T v(args...);
通常情况
T d = T();
当对基于堆栈的对象使用默认构造函数时
T m((iterator(x)), iterator());
对抗 Most Vexing Parse(注意第一个参数周围的额外括号)
T a = { /* some structured values */ };
聚合初始化
取而代之的是发明了统一初始化语法:
T u{ /* whatever */ };
目的是在所有地方使用统一的初始化语法,旧的 stule 将过时。一切都很好,除了 std::initializer_list<S>
初始化的支持者意识到语法应该是这样的:
std::vector<int> vt({ 1, 2, 3 });
std::vector<int> vu{{ 1, 2, 3 }};
这被认为是不可接受的,并且统一的初始化语法被无可挽回地妥协以允许好得多
std::vector<int> vx{ 1, 2, 3 };
这种混合的问题是现在有时不清楚实际意味着什么,统一的初始化语法不再统一。在某些情况下它仍然是必要的(特别是在通用代码中对基于堆栈的对象进行值初始化)但它并不是在所有情况下都是正确的选择。例如,以下两个符号本应表示相同的意思,但它们不是:
std::vector<int> v0(1, 2); // one element with value 2
std::vector<int> v1{1, 2}; // two elements: 1 and 2
tl;dr:初始化列表和统一初始化语法是两个独立的符号。可悲的是,他们发生了冲突。
I don't like this style at all, as it makes me think the object does not have a regular constructor.
如果是聚合类型,则进行聚合初始化。否则,它会考虑构造函数。如果它让你认为 class 是一个集合,那不是语言问题。
Is there a reason to now prefer this syntax over regular constructor calls?
如果您是统一初始化的支持者,是的。如果你不是,你可以坚持旧的风格。请注意,另一个答案谈论 std::initializer_list
但这不适用于您的问题,因为您没有采用 std::initializer_list
的构造函数。 Braced-init-lists 和 std::initializer_list
是不同的概念。最好不要这么早让他们感到困惑。
在 C++ 中,当使用 initializer_list 语法初始化对象时,当没有其他列表初始化规则适用时,对象的常规构造函数也参与重载决策。 据我了解,以下代码调用 X::X(int)
class X { int a_; X(int a):a_(a) {} );
void foo() {
X bar{3};
}
但我不明白,为什么在 initializer_list 的上下文中也会考虑常规构造函数。我感觉现在很多程序员都写X{3}来调用构造函数而不是X(3)来调用构造函数。 我一点都不喜欢这种风格,因为它让我觉得这个对象没有常规的构造函数。
initializer_list 语法也可以用来调用常规构造函数的原因是什么?现在有理由比常规的构造函数调用更喜欢这种语法吗?
本质上是一团糟。对于 C++11,它试图创建一种统一的方法来初始化对象,而不是其他必要的多种方法:
T v(args...);
通常情况T d = T();
当对基于堆栈的对象使用默认构造函数时T m((iterator(x)), iterator());
对抗 Most Vexing Parse(注意第一个参数周围的额外括号)T a = { /* some structured values */ };
聚合初始化
取而代之的是发明了统一初始化语法:
T u{ /* whatever */ };
目的是在所有地方使用统一的初始化语法,旧的 stule 将过时。一切都很好,除了 std::initializer_list<S>
初始化的支持者意识到语法应该是这样的:
std::vector<int> vt({ 1, 2, 3 });
std::vector<int> vu{{ 1, 2, 3 }};
这被认为是不可接受的,并且统一的初始化语法被无可挽回地妥协以允许好得多
std::vector<int> vx{ 1, 2, 3 };
这种混合的问题是现在有时不清楚实际意味着什么,统一的初始化语法不再统一。在某些情况下它仍然是必要的(特别是在通用代码中对基于堆栈的对象进行值初始化)但它并不是在所有情况下都是正确的选择。例如,以下两个符号本应表示相同的意思,但它们不是:
std::vector<int> v0(1, 2); // one element with value 2
std::vector<int> v1{1, 2}; // two elements: 1 and 2
tl;dr:初始化列表和统一初始化语法是两个独立的符号。可悲的是,他们发生了冲突。
I don't like this style at all, as it makes me think the object does not have a regular constructor.
如果是聚合类型,则进行聚合初始化。否则,它会考虑构造函数。如果它让你认为 class 是一个集合,那不是语言问题。
Is there a reason to now prefer this syntax over regular constructor calls?
如果您是统一初始化的支持者,是的。如果你不是,你可以坚持旧的风格。请注意,另一个答案谈论 std::initializer_list
但这不适用于您的问题,因为您没有采用 std::initializer_list
的构造函数。 Braced-init-lists 和 std::initializer_list
是不同的概念。最好不要这么早让他们感到困惑。