【问题标题】:Why do we use initializer_list in C++11?为什么我们在 C++11 中使用 initializer_list?
【发布时间】:2017-01-23 13:19:11
【问题描述】:

我读到initializer_list 是针对采用未知数量的单一类型参数的函数。但我们为什么需要它?为什么我们不能使用普通容器,比如vectorlist

我尝试了以下代码,它可以工作。

#include <iostream>
#include <list>
#include <string>
using namespace std;

void f(const list<string> &slst)
{
    for (auto s : slst)
        cout << s << endl;
}

int main()
{
    f({ "Good", "morning", "!" });
    system("pause");
    return 0;
}

【问题讨论】:

  • 该代码有效因为 std::list 有一个构造函数采用std::initializer_list...

标签: c++ c++11 stl initializer-list


【解决方案1】:

初始化器列表不是替换容器,而是初始化容器。

没有它们,您的示例看起来会有所不同。

int main()
{
    list<string> l;
    l.insert("Good");
    l.insert("Morning");
    l.insert("!");
    f(list);
    system("pause");
    return 0;
}

初始化列表的一点是能够在构造中初始化容器。

【讨论】:

    【解决方案2】:

    简而言之:

    int x[3] = { 1, 2, 3 }; 自 C 以来一直存在。

    但是,int x[] 是有问题的,因为:

    1. 不是类型,所以不支持成员函数
    2. 不可复制

    所以现在我们有了std::array&lt;int, 3&gt; x = { 1, 2, 3 };,它是一个类,所以它可以有成员函数并且是可复制的。它具有与 c 数组一致的初始化符号,这是一个好东西

    事实证明,这也是初始化映射、集合、向量、unordered_maps 等的有用方法。

    实现这一点的机制是std::initializer_list&lt;T&gt;,它将{ 1, 2, 3 } 的字面量转换为具有大小、开始和结束的对象,以便可以遍历它。

    这使得编码更容易、更直观。因为在c++03中,相当于这个:

    std::vector<int> x = { 1, 2, 3 };
    

    这是:

    std::vector<int> make_vector()
    {
      std::vector<int> result;
      result.reserve(3);
      result.push_back(1);
      result.push_back(2);
      result.push_back(3);
      return result;
    };
    
    std::vector<int> x = make_vector();
    

    我想你会同意的,太糟糕了!

    【讨论】:

    • std::arraystd::initializer_list 是正交的;前者是一个聚合。
    • “这不是类型”??
    • @T.C. “对象”可能是一个更好的词。
    • @T.C.实际上,它不是的东西的正确名称是什么?说“数组只是有点笨拙”听起来不专业:)
    • “它没有类类型”,也许?
    【解决方案3】:

    当您使用std::initializer_list 作为参数调用f 时,会隐式转换为std::list。如果你重载f 创建另一个函数void f(std::initializer_list&lt;string&gt;) 像这样:

    #include <iostream>
    #include <list>
    #include <string>
    using namespace std;
    
    void f(const list<string> &slst)
    {
        cout << "1st called" << endl;
    }
    
    void f(const initializer_list<string> &slst)
    {        
        cout << "2nd called" << endl;
    }
    
    int main()
    {
        f({ "Good", "morning", "!" });
        system("pause");
        return 0;
    }
    

    2nd called 将被打印,因为不需要第二次 f 转换。

    【讨论】:

      【解决方案4】:

      虽然您的代码没有明确提及它,但您实际上在 constructor of list 中使用了 initializer_list

      list( std::initializer_list<T> init, 
            const Allocator& alloc = Allocator() );
      

      确实,与自己编写带有 std::initializer_list 参数的函数相比,您可能更有可能使用在其构造函数中接受初始值设定项列表的标准库容器(例如 std::vectorstd::list)。标准库中的另一个示例是聚合函数,如 std::minstd::max,它们根据任意数量的输入值计算单个值。

      但是,在某些情况下,您可能希望将其用于您自己的功能,例如用于您自己实现的数据结构的构造函数,或用于您自己的聚合函数。虽然使用std::vectorstd::list 也可以实现这些事情,但开销最小的最直接方法是使用std::initializer_list

      【讨论】:

        【解决方案5】:

        initializer_list 是围绕 const 自动构建的 C 样式数组的视图包装器。

        它存储一个指向开始的指针和一个指向结束点的指针。

        您对{} 的使用使用std::initializer_list:这就是您的std::list 获得3 个元素的方式!将其编组为 std::list 然后对其进行迭代是额外的间接成本(堆分配、额外副本)。

        直接在f 中使用initializer_list 可以避免这些额外费用,让您可以访问由{} 使用创建的3 个元素的原始数组。

        基本上,std::initializer_list 是语言组件的库组件,它允许非 C 样式数组访问通过简单 {} 创建的所有相同类型的元素列表。

        您只是偶尔使用它们来替换容器,主要是在类似初始化的操作期间,因为数组的生命周期不能超出最初创建的列表的生命周期。如果您想要比这更复杂的数据生命周期,则必须将其复制出来。

        【讨论】:

          猜你喜欢
          • 2023-03-07
          • 1970-01-01
          • 1970-01-01
          • 2021-10-15
          • 2019-02-16
          • 2018-11-19
          • 1970-01-01
          • 1970-01-01
          • 2011-02-05
          相关资源
          最近更新 更多