【问题标题】:When is a vector copied, when is a reference passed?什么时候复制一个向量,什么时候传递一个引用?
【发布时间】:2013-02-13 17:49:14
【问题描述】:

我正在使用一个经常使用std::vector 的程序。还有很多分配/解除分配发生,数十亿,我试图尽可能多地避免它们。 由于我对 C++ 比较陌生,因此我对使用向量时发生的分配有一些疑问(例如,向其添加元素时)。 我在 Win7 64 位机器上,程序是 32 位,我正在使用当前版本的 MinGW 编译器。

我想知道,在以下情况下会发生什么,即如果向量被复制,作为引用传递,...

1.

std::vector<T> fillVector() {
    std::vector<T> returnVector;
    ...
    return returnVector;
}

std::vector<T> myVector = fillVector();

2.

 std::vector<T>& fillVector() {
    std::vector<T>* returnVector = new std::vector<T>;
    ...
    return (*returnVector);
}

std::vector<T> myVector = fillVector();

3.

std::vector<T>* fillVector() {
    std::vector<T>* returnVector = new std::vector<T>;
    ...
    return returnVector;
}

std::vector<T>* myVector = fillVector();

还有以下不同的操作:

4.

std::vector<T> myVector1;
... (myVector1 being filled)
std::vector<T> myVector = myVector1;

5.

std::vector<T>* myVector1 = new std::vector<T>;
... (myVector1 being filled)
std::vector<T> myVector = (*myVector1);

假设我不想将myFunction/ 中的参数更改为myVector 中的myFunction 不会损害程序的其余部分:

6.

void myFunction(std::vector<T> myParam) {
   ...
}

std::vector<T> myVector;
... (myVector being filled)
myFunction(myVector);

7.

void myFunction(std::vector<T>& myParam) {
   ...
}

std::vector<T> myVector;
... (myVector being filled)
myFunction(myVector);

如果我的理解是正确的,最快的选项(意味着传递引用而不是创建副本并传递它们)将是 2/3、5 和 7。如果我错了,请纠正我!

【问题讨论】:

  • 最快最干净的是选项1。

标签: c++ stl stdvector


【解决方案1】:

最快和最惯用的是选项 1。编译器几乎肯定会忽略两个副本(从returnVector 到返回值和从返回值到myVector)。复制省略是编译器可能进行的一种优化,涉及删除任何不必要的副本。在这里,两个副本都是不必要的,std::vector 将直接构建以代替 myVector

事实上,即使您使用编译器禁用复制省略优化,在 C++11 中,两个副本实际上都是移动的。移动std::vector 需要一些任务并且非常快。第一个被特殊规则认为是移动,第二个是移动,因为表达式fillVector() 是一个右值表达式。

【讨论】:

  • 这解释了我所做的一些实验,这对我来说真的没有意义。
【解决方案2】:

1.

std::vector<T> fillVector() {
    std::vector<T> returnVector;
    ...
    return returnVector;
}

std::vector<T> myVector = fillVector();

这很好。 vector 按值返回,但大多数编译器(至少在启用优化时)在(命名的)返回值优化下忽略了对复制构造函数的调用。

此外,在 C++11 中,移动语义确保调用移动构造函数而不是复制构造函数,这将简单地窃取返回向量的内容,而不会生成昂贵的副本。

2.

 std::vector<T>& fillVector() {
    std::vector<T>* returnVector = new std::vector<T>;
    ...
    return (*returnVector);
}

std::vector<T> myVector = fillVector();

不要这样做。动态分配的不必要开销,加上必须记住必须解除分配返回对象的负担。避免手动内存管理,更喜欢 1。

3.

std::vector<T>* fillVector() {
    std::vector<T>* returnVector = new std::vector<T>;
    ...
    return returnVector;
}

std::vector<T>* myVector = fillVector();

同上。避免手动内存管理。

4.

std::vector<T> myVector1;
... (myVector1 being filled)
std::vector<T> myVector = myVector1;

这是一个概念上不同的操作。在这里您想要创建一个副本,看来您做得对。在 C++11 中,如果您只需要传输 myVector1 的内容而不是复制它,您可能想要使用std::vector&lt;T&gt; myVector = std::move(myVector1)

5.

std::vector<T>* myVector1 = new std::vector<T>;
... (myVector1 being filled)
std::vector<T> myVector = (*myVector1);

与上述相同,您想要创建一个副本,但您不必要地动态分配向量。这将再次迫使您手动处理其生命周期,这是不好且容易出错的。不要这样做。

6.

void myFunction(std::vector<T> myParam) {
   ...
}

std::vector<T> myVector;
... (myVector being filled)
myFunction(myVector);

这里你通过值传递myVector。这是否可以优化取决于myFunction 应该对其参数做什么:它应该改变它吗?如果是这样,您是否希望这些更改在从函数返回后可见?如果是,则按值传递是正确的,并且没有办法对其进行优化除非您想获得myVector 对象:在这种情况下,在 C++11 中您可以 将其传递给函数时将其移动。这将避免昂贵的、不必要的复制。

7.

void myFunction(std::vector<T>& myParam) {
   ...
}

std::vector<T> myVector;
... (myVector being filled)
myFunction(myVector);

这将通过引用传递,只要从函数返回后可以看到myFunctionmyVector的副作用就可以了。一般不能说这是否正确,这取决于您的应用程序的特定逻辑。

【讨论】:

  • 感谢您的详细回答。我编辑了关于 6 和 7 的第一篇文章:假设在 myFunction 中对 myVector 所做的更改不会损害程序的其余部分,然后通过引用传递肯定会更快,对吧?
  • @MrWayne:在这种情况下,是的。那会更快。如果您将“不会受伤”改为“必须看到”,这也可能是唯一正确的方法
猜你喜欢
  • 2011-09-20
  • 2019-09-04
  • 1970-01-01
  • 1970-01-01
  • 2014-04-29
  • 2016-01-31
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多