尝试理解 C++ STL 容器的初始化

Trying to Understand the Initialization of C++ STL Containers

我想了解 C++ STL 容器的初始化。这是我的噩梦:

    vector<int> V0 ({ 10, 20 });    // ok - initialized with 2 elements
    vector<int> V1 = { 10, 20 };    // ok - initialized with 2 elements
    vector<int> V2 = {{ 10, 20 }};  // ok - initialized with 2 elements
    vector<int> V3 = {{ 10 }, 20 }; // ok - initialized with 2 elements
    vector<int> V4 { 10, 20 };      // ok - initialized with 2 elements
    vector<int> V5 {{ 10, 20 }};    // ok - initialized with 2 elements
    vector<int> V6 {{ 10 }, 20 };   // ok - initialized with 2 elements
    queue<int> Q0 ({ 10, 20 });     // ok - initialized with 2 elements
 // queue<int> Q1 = { 10, 20 };     // compile error
 // queue<int> Q2 = {{ 10, 20 }};   // compile error
 // queue<int> Q3 = {{ 10 }, 20 };  // compile error
 // queue<int> Q4 { 10, 20 };       // compile error
    queue<int> Q5 {{ 10, 20 }};     // ok - initialized with 2 elements
 // queue<int> Q6 {{ 10 }, 20 };    // compile error

我们正在谈论 C++11。

我做了一些研究,这是我的问题:

  1. 我认为编译错误是由于缺少 queue<T>initializer_list 构造函数造成的。参见 vector<T>http://www.cplusplus.com/reference/vector/vector/vector/queue<T>http://www.cplusplus.com/reference/queue/queue/queue/我说得对吗?
  2. 现在对于从V0V6的所有向量,我理解V0V1V4。有人可以帮助我理解 V2V3V5V6 吗?
  3. 我不太明白Q0Q5。有人可以帮助我吗?

我也在读 Mike Lui 的文章:Initialization in C++ is Seriously Bonkers。我想和大家分享一下,但是有没有一种快速的方法可以帮助我理解这个噩梦? :-)

这些都没有 "nightmarish"。您只需要阅读您所写的内容。更具体地说,你必须系统地从外到内地遵守规则。

vector<int> V0 ({ 10, 20 });

调用一个 vector 构造函数(这就是 () 的意思),向它传递一个单一的花括号初始化列表。因此,它将选择一个带有一个值的构造函数,但只选择其第一个参数可以由包含整数的 2 元素花括号初始化列表初始化的构造函数。比如vector<int>包含的initializer_list<int>构造函数。

vector<int> V1 = { 10, 20 };

列表初始化(当你直接用 braced-init-list 初始化一个东西时会发生这种情况)。在列表初始化规则下,首先考虑采用单个 initializer_list 参数的所有类型的构造函数。系统会尝试直接用 braced-init-list 初始化这些构造函数;如果其中一个候选构造函数成功,则调用该构造函数。

显然,您可以从 2 元素大括号初始化列表中初始化一个 initializer_list<int>。这是 vector 中唯一的 initializer_list 构造函数,因此它被调用。

vector<int> V2 = {{ 10, 20 }};

还是列表初始化。同样, initializer_list 构造函数与 braced-init-list 中的值匹配。但是,braced-init-list 中的 "value" 本身就是另一个 braced-init-list。 int 无法从 2 元素花括号初始化列表初始化,因此 initializer_list<int> 无法由 {{10, 20}} 初始化。

由于没有initializer_list构造函数可以使用,所有构造函数在正常函数重载决议规则下被考虑。在这种情况下,(外部)braced-init-list 的成员被视为该类型构造函数的参数。外部花括号初始化列表中只有一个值,因此只考虑可以用一个参数调用的构造函数。

系统将尝试使用内部花括号初始化列表初始化所有此类构造函数的第一个参数。并且有一个构造函数,其参数可以由整数的 2 元素花括号初始化列表初始化。即 initializer_list<int> 构造函数。也就是说,虽然 initializer_list<int> 不能被 {{10, 20}} 初始化,但它可以被 {10, 20}.

初始化
vector<int> V3 = {{ 10 }, 20 };

同样,还是列表初始化。同样,我们首先尝试将完整的 braced-init-list 应用于该类型的任何 initializer_list 构造函数。 initializer_list<int> 可以从 {{10}, 20} 的花括号初始化列表中初始化吗?是的。这就是发生的事情。

为什么这行得通?因为任何类型 T 是 copyable/moveable 总是可以从包含该类型的某些值的花括号初始化列表中初始化。也就是说,如果 T t = some_val; 有效,那么 T t = {some_val}; 也有效(除非 T 有一个采用 Tinitializer_list 构造函数,这显然很奇怪)。如果 T t = {some_val}; 有效,那么 initializer_list<T> il = {{some_val}};.

也有效
vector<int> V4 { 10, 20 };      // ok - initialized with 2 elements
vector<int> V5 {{ 10, 20 }};    // ok - initialized with 2 elements
vector<int> V6 {{ 10 }, 20 };   // ok - initialized with 2 elements

这些与 1、2 和 3 相同。列表初始化通常称为 "uniform initialization",因为直接使用 braced-init-list 和使用 = braced-init-list 之间(几乎)没有区别.唯一有区别的是显式构造函数是否被 selected 或如果您使用 auto 和 braced-init-list 中的单个值。


queues initializer_list 构造函数不是 "missing"。它不是故意存在的,因为 queue 不是 容器 。它是容器适配器类型。它存储一个容器,并适配容器的接口以限制队列操作:push、pop 和 peek。

所以其中 none 应该 有效。

queue<int> Q0 ({ 10, 20 });

这使用常规的旧重载解析调用 queue<int> 的构造函数,就像 V0 一样。唯一的区别是构造函数 it selects 是采用队列容器类型的构造函数。由于您没有指定容器,它使用默认值:std::deque<int>,它可以从两个整数的 braced-init-list 构造。

queue<int> Q5 {{ 10, 20 }};

情况与 V2 相同。 queue 上没有 initializer_list 构造函数,因此它的行为与 Q0 完全相同:对 select 参数可以采用 braced-init-list 2 的构造函数使用重载决策整数。