我们可以在哪里使用列表初始化?

2022-01-18 00:00:00 initialization aggregate c++ c++11

这个问题已经涵盖了 POD 和聚合是什么,并提供了一些聚合初始化的示例.

This question already covers what PODs and aggregates are, and provides some examples on aggregate initialization.

这里的问题是在哪里可以使用列表初始化?

还有你可以在哪里使用(没有更好的术语)列表分配?

Also where can you use (in lack of a better term) list assignment?

答案应该同时涉及 C++03 和 C++11,突出它们之间的差异.

An answer should deal with both C++03 and C++11, highlighting the differences between them.

推荐答案

C++03

列表初始化

在 C++03 中,您只能对聚合 (C++03 [dcl.init.aggr]) 和标量 (C++03 [dcl.init]/13) 类型使用列表初始化:

C++03

List initialization

In C++03 you can only use list-initialization for aggregates (C++03 [dcl.init.aggr]) and scalar (C++03 [dcl.init]/13) types:

int i = { 0 };
POD pod = { 0, 1, 2 };

列表赋值

您不能在 C++03 的任何地方使用列表赋值".[expr.ass]/1 中显示的语法不允许在赋值右侧使用花括号.

List assignment

You could not use "list-assignment" anywhere in C++03. The grammar shown in [expr.ass]/1 does not allow a braced list on the right of an assignment.

在 C++11 中,您几乎可以在任何可以创建变量的地方使用列表初始化(参见 C++11 中的 [dcl.init] 和 [dcl.init.list]/1,其中列出了 list-允许初始化)例如

In C++11 you can use list-initialization pretty much anywhere you can create a variable (see [dcl.init] in C++11 and [dcl.init.list]/1 which lists contexts where list-initialization is allowed) e.g.

struct Base { };

struct Class : Base
{
    int mem{ 0 };  // init non-static data member

    Class(int i)
    : Base{}   // init base class
    , mem{i}   // init member
    {
      int j{i};   // init local var

      int k = int{0};  // init temporary

      f( { 1 } );  // init function arg

      int* p = new int{1};  // new init

      // int k(int());  // most vexing parse, declares function
      int k{ int{} };   // ok, declares variable

      int i[4]{ 1,2,3,4 };   // init array
    }

    Class f(int i)
    {
      return { i };   // init return value
    }
};

Class c{1};   // init global var

上面的大多数初始化都声明了一个 intint 数组,但是可以使用相同的语法来调用类类型的构造函数(例如构造一个Class变量)

Most of the initializations above declare an int or array of int but the same syntax can be used to call a constructor for a class type (like the two lines that construct a Class variable)

除了在几乎任何可以初始化变量的上下文中都有效之外,列表初始化还可以与 C++11 的另一个新特性很好地交互:std::initializer_list 类模板.接受 std::initializer_list 参数的构造函数可以传递任意长的值列表,构造函数可以通过 begin()end 对其进行迭代() std::initializer_list 的成员函数.这个新特性的主要好处是它允许你用一组元素初始化一个容器,例如向量v{ 0, 1, 2, 3, 4, 5 } 而不是构造容器然后插入值.

As well as being valid in almost any context where you can initialize a variable, list-initialization also interacts well with another new feature of C++11: the std::initializer_list class template. A constructor that takes a std::initializer_list argument can be passed an arbitrarily-long list of values, which the constructor can iterate over via begin() and end() member functions of the std::initializer_list. The main benefit of this new feature is that it allows you to initialize a container with a set of elements, e.g. vector<int> v{ 0, 1, 2, 3, 4, 5 } rather than constructing the container and then inserting values.

列表初始化也可以用于括号初始化列表中的元素,允许嵌套列表初始化,例如Map m{ {a, b}, {c, d} } 而不是 Map m{ Map::value_type(a, b), Map::value_type(c, d) }

List-initialization can also be used for elements within a braced-init-list, allowing nested list-initialization e.g. Map m{ {a, b}, {c, d} } rather than Map m{ Map::value_type(a, b), Map::value_type(c, d) }

唯一一次列表初始化没有做正确的事情是,当类有另一个构造函数以 std::initializer_list 作为列表时,尝试通过调用构造函数来构造类类型-initialization 总是喜欢构造函数采用 std::initializer_list 例如

The only time list-initialization doesn't do the right thing is when trying to construct a class type by calling a constructor if the class has another constructor taking a std::initializer_list, as list-initialization will always prefer the constructor taking a std::initializer_list e.g.

// attempts to create vector of 5 elements, [1,1,1,1,1]
// but actually creates a vector with two elements, [5,1] 
std::vector<int> v{ 5, 1 };

这不会调用 vector(size_type, const int&) 构造函数,而是调用 vector(initializer_list<int>) 构造函数.

This doesn't call the vector(size_type, const int&) constructor, instead of calls the vector(initializer_list<int>) constructor.

在 C++11 中,您可以使用列表赋值"

In C++11 you can use "list-assignment"

  • 分配给标量类型时,如果 braced-init-list 有一个可转换(不缩小)为变量类型的元素(参见 [expr.ass]/9)
  • 当赋值的左操作数是具有用户定义的赋值运算符的类类型时,在这种情况下braced-init-list用于初始化运算符的参数(见 [expr.ass]/9).这包括两种情况,如 operator=(std::initializer_list<T>),其中右侧操作数中的 braced-init-list 的元素可转换为 T,例如对于 std::vector<int>上面的 vv = { 1, 2, 3 } 会将容器的内容替换为 [1,2,3] 并且当 braced-init-list 可以通过合适的构造函数隐式转换为运算符的参数类型,例如

  • when assigning to a scalar type, if the braced-init-list has a single element that is convertible (without narrowing) to the variable's type (see [expr.ass]/9)
  • when the left operand of the assignment is a class type with a user-defined assignment operator, in which case the braced-init-list is used to initialize the argument of the operator (see [expr.ass]/9). This includes both cases like operator=(std::initializer_list<T>) where the elements of the braced-init-list in the right operand are convertible to T, e.g. for the std::vector<int> v above, v = { 1, 2, 3 } will replace the container's contents with [1,2,3] and when the braced-init-list can be implicitly-converted to the operator's argument type, via a suitable constructor e.g.

struct A {
  int i;
  int j;
};

struct B {
  B& operator=(const A&);
};

int main() {
  B b;
  b = { 0, 1 };
}

main 的最后一行,braced-init-list 将被隐式转换为临时 A,然后是赋值运算符B 将使用该临时参数作为参数调用.

On the last line of main the braced-init-list will be implicitly-converted to a temporary A then the assignment operator of B will be called with that temporary as its argument.

相关文章