【问题标题】:C++ vector, return vs. parameter [duplicate]C ++向量,返回与参数[重复]
【发布时间】:2010-09-13 18:30:26
【问题描述】:

可能重复:
how to “return an object” in C++

我想知道以下三种方法之间是否有区别:

void FillVector_1(vector<int>& v) {
    v.push_back(1); // lots of push_backs!
}

vector<int> FillVector_2() {
    vector<int> v;
    v.push_back(1); // lots of push_backs!
    return v;
}

vector<int> FillVector_3() {
    int tab[SZ] = { 1, 2, 3, /*...*/ };
    return vector<int>(tab, tab + SZ);
}

【问题讨论】:

  • 当你觉得这实际上是一个问题时,只需按值返回并担心性能。然后对其进行概要分析,这样您就不必猜测了。我以duplicate 的身份抢先关闭。
  • 由于以下几个原因,我不会投票决定将其作为重复项关闭:(1) 较早的问题有关于动态内存分配的细节不适用于这个问题,以及 (2) 这个question 是特定于 STL 的,它可以调用其他惯用语,例如插入迭代器。此外,STL 的一些实现已经利用 C++0x r-value 引用来最小化复制。该方法不适用于前面的问题。
  • @GMan:虽然我同意不要过早进行微优化的观点,但我不同意理所当然应该按值返回大型对象的观点。特别是当涉及到 STL 集合时,有更好的方法。例如,通过编写函数来接受一个输出迭代器,我们可以使其更具适应性和更多的 STL 风格,同时避免不得不依赖 RVO 或时髦的编译器优化,这些优化可能会以不合需要的方式改变程序的行为。此外,C++ 程序员应该知道他们的对象何时被复制——而不是代码和祈祷
  • @John:这不是代码和祈祷,而是代码和让你的程序工作然后-profile-it-to-see-what-c​​an-be-sped-up-and-别再胡思乱想了。 @Adrian @John:迭代器设计确实可能是最好的编码方式,但就目前而言,问题本身显然只是“哦,我当然不能按价值返回!”的毫无根据的情况。但是,当我喜欢超越问题的表面并解决“真正的”问题时,您是对的;我正在投票重新开放。

标签: c++ stl vector


【解决方案1】:

最大的区别是第一种方式追加到现有内容,而其他两种方式填充一个空向量。 :)

我认为您正在寻找的关键字是return value optimization,这应该是相当常见的(对于 G++,您必须专门将其关闭以防止它被应用)。也就是说,如果用法是这样的:

vector<int> vec = fill_vector();

那么可能很容易没有复制(并且该功能更易于使用)。

如果您正在使用现有向量

vector<int> vec;
while (something)
{
    vec = fill_vector();
    //do things
}

然后使用 out 参数将避免在循环中创建向量并复制数据。

【讨论】:

  • +1 用于指出初始化和赋值 w.r.t 之间的区别。复制省略。
【解决方案2】:

惯用的 C++ 方法是使用输出迭代器对容器类型进行抽象:

template<typename OutputIterator>
void FillContainer(OutputIterator it) {
    *it++ = 1;
    ...
}

那么它可以与vector一起使用:

std::vector<int> v;
FillContainer(std::back_inserter(v));

性能(以及其他优点,例如能够填充非空容器)与您的选项 #1 相同。另一个好处是,这可用于以流模式输出,如果使用适当类型的迭代器(例如ostream_iterator),结果会立即处理并丢弃而不存储。

【讨论】:

  • +1。这似乎是最“STL 方式”。请注意,从 FillContainer 返回迭代器可能很有用。
  • 当“FillContainer”是一个类的方法时,它是如何工作的?
  • @User:同样的方式——类成员函数也可以被模板化。你不能用这样的东西把它变成虚拟的。
【解决方案3】:

人们会假设参数是最好的,但实际上它通常不是。这取决于编译器。一些编译器(我认为实际上是最新的编译器)将适用 Return Value Optimization - Visual Studio 2005 和更高版本在您提供的两种情况下都应该这样做(参见 Named Return Value Optimization in Visual C++ 2005)。

确定的最好方法是检查产生的反汇编。

【讨论】:

  • 这是一个巨大的优化,大多数现代编译器都利用它来大幅提升性能。
  • RVO 的整个想法让我畏缩不前。我讨厌它。
【解决方案4】:

在组合中添加第四个变体:

void FillVector_4(vector<int>& v) {
   static const int tab[SZ] = {1,2,3, ... };
   v.assign(tab,tab+SZ);
}

如果您正在考虑性能版本 2 和版本 3,可以让编译器为返回值创建一个 vector&lt;int&gt; 副本。也就是说,如果编译器无法进行 NRVO(命名返回值优化)。此外,没有reserve 的连续push_backs 可能会导致几次重新分配,因为向量需要增长。这是否重要取决于您要解决的问题。

您会很高兴知道 C++0x 将使返回本地创建的向量非常有效。我还建议阅读 David Abrahams article series,了解包括传递/返回在内的有效值类型。

【讨论】:

    【解决方案5】:

    第一个绝对不复制向量。

    复制向量的成本可能与向量中的元素数量成线性关系。

    第一个在任何平台或编译器上都不会引入线性行为的风险,也没有分析和重构的成本。

    【讨论】:

    • 第一个还允许连续调用多个函数来填充单个向量。如果您需要对 #2 执行相同操作,则必须从返回的向量中复制元素(这意味着编译器所做的所有英勇的 RVO 努力都将徒劳无功,因为您将立即得到 -无论如何都丢弃了副本)。
    【解决方案6】:

    一目了然,前两个可能对向量进行了更多的调整,而第三个可能不需要在运行时调整向量的大小。这可以通过在回击之前自行调整大小来缓解。

    【讨论】:

      猜你喜欢
      • 2014-08-02
      • 2015-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-25
      • 2023-03-09
      相关资源
      最近更新 更多