初始化列表 vs. 向量

25

在C++11中,可以使用初始化列表来初始化函数中的参数。它的目的是什么?使用const向量不能完成相同的任务吗?下面两个程序有什么区别?

使用初始化列表:

#include <iostream>

using namespace std;

int sumL(initializer_list<int> l){
    int sum = 0;
    for (const auto i: l){
        sum += i;
    }
    return sum;
}

int main(){

    cout << sumL({1, 2, 3}) << "\n";

    return 0;
}

使用const向量:

#include <iostream>
#include <vector>

using namespace std;

int sumV(const vector<int> l){
    int sum = 0;
    for (const auto i: l){
        sum += i;
    }
    return sum;
}

int main(){

    cout << sumV({1, 2, 3}) << "\n";
    return 0;
}

2
第二段代码需要的东西比实际要用的多(而且向量也是用initializer_list构造的)。 - user2249683
离题:请注意你复制了向量,即你没有使用引用&。在for循环中也是如此,现在只是一个int,但对于较大的对象,引用将避免你复制向量中的每个对象。 - Michiel uit het Broek
5个回答

19

std::initializer_list 常用于容器(及类似结构)类的构造函数参数,方便从同一类型的少量对象初始化这些容器。当然,您也可以使用 std::initializer_list 来使用相同的 {} 语法。

由于 std::initializer_list 具有固定的大小,因此不需要动态分配空间,可以有效地实现。而另一方面,std::vector 则需要动态内存分配。即使在简单示例中,编译器也不太可能优化掉这种开销(避免中间的 std::vector 及其动态内存分配)。除此之外,在程序输出方面没有区别(虽然您应该传递一个 const std::vector<int>& 参数以避免副本及其关联的动态内存分配)。


12

initializer_list使用最优的存储位置并防止不必要的调用,它被设计为轻量级,在使用vector时会有堆分配和可能会有更多的复制/移动。


7
两者的语义有很大的区别。initializer_list具有指针语义,而vector具有值语义。
在您的第一个示例中,编译器将生成类似以下代码的代码:
int const __temp_array[3] = {1, 2, 3};
cout << sumL(std::initializer_list<int>(__temp_array, __temp_array + 3)) << "\n";

这在[dcl.init.list]/5中有解释。如您所见,在sumL中,您可以访问到braced-init-list的const指针,这意味着您别无选择,只能将元素从列表中复制出来。
对于sumV,如果需要(假设参数类型不是const),您可以使用std::moved从vector中移动元素。
类似地,复制initializer_list执行浅层复制,即仅复制指针,而复制vector当然意味着要复制元素。
在您的示例中,除了构造vector需要动态内存分配外,上述任何一点都没有任何区别,而构造initializer_list则不需要。

谢谢您提供的详细信息。请为我澄清一件事。如果我说 initializer_list<object> il1 = {...}; 将对象存储在容器内(或其他地方,例如堆栈?),然后它的副本 initializer_list<object> il2 = il1; 存储对象的链接,这样每个 il 的副本都将共享相同的对象,我的理解是正确的吗? - kyb
答案是肯定的。 - kyb

7

initalizer_list不像std::vector一样是一个通用的容器。它的主要目的是对象初始化。如果低开销和无堆分配对您有吸引力,我建议看看std::array。它是一个固定大小的栈分配数组,具有STL容器的所有便利性,实际上是在c数组上面的一个薄包装。


0

初始化列表

使用“花括号”语法创建的轻量级类似数组的元素容器。例如,{ 1, 2, 3 } 创建了一个整数序列,其类型为 std::initializer_list。作为将对象向函数传递的向量的替代品非常有用。

  int sum(const std::initializer_list<int>& list) {
  int total = 0;
  for (auto& e : list) {
    total += e;
  }

  return total;
}

auto list = {1, 2, 3};
sum(list); // == 6
sum({1, 2, 3}); // == 6
sum({}); // == 0

然而,在您的情况下,如果您像您现在这样做,编译器很可能会像下面提到的向量函数实现一样进行优化。

template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // copy list-initialization in return statement
                                   // this is NOT a use of std::initializer_list
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接