返回std :: list代价高昂?

时间:2009-07-07 14:07:05

标签: c++ performance list stl return-value

我想知道返回一个列表而不是返回一个指针,在性能方面是昂贵的,因为如果我记得,列表没有很多属性(是不是像3个指针?一个用于当前位置,一个用于开头,一个用于结束?)。

6 个答案:

答案 0 :(得分:34)

如果按值返回std::list,它将不仅复制列表头,它将复制列表中每个项目的一个列表节点。所以,是的,对于一个大的清单,它是昂贵的。

如果列表是在返回它的函数中构建的,那么您可以从命名的返回值优化中受益,以避免不必要的复制。但这是特定于您的编译器的。如果例如在调用函数之前已存在列表(例如,如果它是对象的成员变量),则它永远不会应用。

为避免按值返回容器,C ++中常见的习惯用法是将输出迭代器作为参数。所以而不是:

std::list<int> getListOfInts() {
    std::list<int> l;
    for (int i = 0; i < 10; ++i) {
        l.push_back(i);
    }
    return l;
}

你这样做:

template<typename OutputIterator>
void getInts(OutputIterator out) {
    for (int i = 0; i < 10; ++i) {
        *(out++) = i;
    }
}

然后来电者做:

std::list<int> l;
getInts(std::back_inserter(l));

一旦编译器完成内联和优化,代码通常或多或少相同。

这样做的好处是调用者不依赖于特定的集合 - 例如,如果对特定情况更有用,他可以将项目添加到向量而不是列表中。如果他只需要查看每个项目一次,而不是将它们全部放在一起,那么他可以通过使用他自己设计的输出迭代器在流模式下处理它们来节省内存。

缺点与任何模板代码相同:在编译时,调用者必须可以使用实现,并且最终可以为模板的多个实例化提供大量“重复”对象代码。当然,您可以使用相同的模式而不使用模板,通过将函数指针(如果需要,加上用户数据指针)作为参数并使用每个项调用一次,或者通过使用纯虚拟成员定义IntVisitor抽象类函数,让调用者提供它的实例。

[编辑:T.E.D在评论中指出,另一种避免复制而不使用模板的方法是让调用者通过引用传入列表。这肯定有效,它只是给调用者不如模板的灵活性,因此不是STL使用的习语。如果你不想要我上面描述的“优势”这是一个很好的选择。然而,STL背后的原始意图之一是将“算法”(在这种情况下,无论什么决定值)与“容器”分开(在这种情况下,值恰好存储在列表中,而不是到一个向量或一个数组或一个自我排序集,或者只打印出来而不存储它们。)

答案 1 :(得分:5)

它(一如既往)取决于。可以通过以下代码中的return来调用复制构造函数。

std::list<int> foo() {
  std::list<int> bar;
  // ...
  return bar;
};

如果编译器应用return value optimization,则可能无法调用它。如果调用了复制构造函数,那么相对于较大列表的指针,它可能更昂贵,如果没有调用它,那么返回直列表会更快(因为它避免了动态分配)

就个人而言,我不担心它并返回直接列表。然后,只有当我的探查器说这个问题时我才考虑进行优化。

答案 2 :(得分:2)

如果按值返回,将调用复制构造函数,并逐个复制项目。有时你会被命名值优化保存,因为onebyone指出。

确保副本不会发生的主要选项是:

  • 通过引用传入列表以由函数填写。这样你就告诉函数把数据放在哪里,不需要复制,因为你把它放在最后的位置。
  • 在堆上分配一个列表并将其返回。你应该在一个像std :: auto_ptr或boost :: shared_ptr这样的智能指针中返回它,以确保它被删除并且是异常安全的。

答案 3 :(得分:0)

我相信,拷贝构造函数被调用。

答案 4 :(得分:0)

它可能很昂贵,因为它会复制列表中的每个元素。更重要的是,它有不同的行为:你想要一个列表的副本,还是想要一个指向原始列表的指针?

答案 5 :(得分:-1)

您可以编写自己的复制构造函数,因此不会复制。