什么时候会复制向量,什么时候会传递引用?

7
我正在使用一个频繁使用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,这样做不会影响程序的其余部分:

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
最快和最干净的选项是选项1。 - juanchopanza
2个回答

7

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();

不要这样做。动态分配会增加额外的开销,而且还需要记住你必须释放返回的对象。避免手动内存管理,应优先使用其他方法。
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;

这是一项概念上不同的操作。在这里,您想要创建一个副本,并且似乎您正在正确地执行此操作。如果您只需要转移myVector1的内容而不是复制它,则可以在C++11中使用std::vector<T> myVector = std::move(myVector1)
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中将其移动到函数时。这将避免昂贵和不必要的复制。
void myFunction(std::vector<T>& myParam) {
   ...
}

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

这将通过引用传递,并且只要在从函数返回后查看myVector上的myFunction的副作用是可以的。一般情况下无法确定是否正确,这取决于您应用程序的特定逻辑。


感谢您详细的回答。我已经编辑了我的第一个帖子,关于6和7:假设在myFunction内对myVector所做的更改不会影响程序的其余部分,那么通过引用传递肯定会更快,对吗? - MrWayne
@MrWayne:在这种情况下,是的。那样会更快。如果你把“不会伤害”的说法改成“必须被看到”,那也可能是唯一正确的方法。 - Andy Prowl

1
最快且最惯用的选项是选项1。从returnVector到返回值以及从返回值到myVector的两个副本几乎肯定会被编译器省略。复制省略是编译器可能进行的一种优化,涉及删除任何不必要的副本。在这里,两个副本都是不必要的,std::vector将直接构造在myVector的位置。
事实上,即使您使用编译器禁用了复制省略优化,在C++11中,这两个副本实际上也将是移动操作。移动一个std::vector需要一些赋值,并且非常快速。第一个被认为是移动的特殊规则,第二个是移动的,因为表达式fillVector()是一个右值表达式。

这解释了我做的一些实验,它们对我来说真的没有意义。 - MrWayne

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