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

Where can we use list initialization?

本文关键字:列表 初始化 在哪里 我们      更新时间:2023-10-16

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

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

另外,您可以在哪里使用(缺乏更好的术语)列表分配?

答案应该同时处理C++03和C++11,突出它们之间的差异。

C++03

列表初始化

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

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

列表分配

您不能在 C++03 的任何地方使用"列表分配"。[expr.ass]/1 中显示的语法不允许在作业右侧使用大括号列表。

C++11

列表初始化

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

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变量的两行)

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

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

列表初始化唯一不做正确事情的情况是,如果类有另一个构造函数std::initializer_list,则尝试通过调用构造函数来构造类类型,因为列表初始化将始终更喜欢构造函数采用std::initializer_list

例如
// 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>)构造函数。

列表分配

在 C++11 中,您可以使用"列表分配">

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

    struct A {
    int i;
    int j;
    };
    struct B {
    B& operator=(const A&);
    };
    int main() {
    B b;
    b = { 0, 1 };
    }
    

    main的最后一行,大括号的初始化列表将被隐式转换为临时A然后调用B赋值运算符,并将使用该临时运算符作为其参数。

聚合初始化是列表初始化的子集,仅限于聚合和 POD(如您引用的问题中所述)。 这两种类型的初始化都使用大括号和可选的和等于,因此语法在初始化时看起来相同。 有关更多详细信息,包括可以使用每种初始化形式的位置,请参阅 http://en.cppreference.com/w/cpp/language/aggregate_initialization 和 http://en.cppreference.com/w/cpp/language/list_initialization。

在 C++03 中,聚合初始化只能与 equals 一起使用(即 T 对象 {arg1, arg2};仅 T 对象 = {arg1, arg2};) 无效,而 C++11 允许它没有等于(即 T 对象 {arg1, arg2};变得有效)。 同样在 C++11 中,聚合初始化略有修改,以不允许聚合初始化中的缩小转换。

列表初始化的子集(不是聚合初始化子集)是在 C++11 中引入的。

List初始化可用于初始化动态分配的数组(C++11):

int * a = new int[3] {4, 3, 2};

一个非常漂亮的功能在 C++03 中是不可能的。