6

在 C++ 中,当使用 initializer_list 语法初始化对象时,当没有其他列表初始化规则适用时,对象的常规构造函数也参与重载决策。据我了解,以下代码调用 X::X(int)

class X { int a_; X(int a):a_(a) {} );

void foo() {
   X bar{3};
}

但我不明白,为什么在 initializer_lists 的上下文中也考虑常规构造函数。我感觉现在很多程序员写 X{3} 来调用构造函数,而不是 X(3) 来调用构造函数。我根本不喜欢这种风格,因为它让我觉得对象没有常规的构造函数。

initializer_list 语法也可以用来调用常规构造函数的原因是什么?现在有理由比常规构造函数调用更喜欢这种语法吗?

4

2 回答 2

21

本质上是一团糟。对于 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:初始化列表和统一初始化语法是两个独立的符号。可悲的是,它们发生了冲突。

于 2017-12-28T10:00:56.553 回答
0

我根本不喜欢这种风格,因为它让我觉得对象没有常规的构造函数。

如果是聚合类型,则执行聚合初始化。否则,它会考虑构造函数。如果它让你认为这个类是一个聚合,那不是语言问题。

现在有理由比常规构造函数调用更喜欢这种语法吗?

如果你是统一初始化的支持者,是的。如果你不是,你可以坚持旧的风格。请注意,另一个答案是关于std::initializer_list,但这不适用于您的问题,因为您没有构造函数需要std::initializer_list. Braced-init-liststd::initializer_list是独立的概念。最好不要这么早就让他们感到困惑。

于 2017-12-28T10:18:23.493 回答