如何在方法主体中返回声明向量的引用?

How to return the reference of a declared vector in the method body?

本文关键字:向量 声明 引用 返回 方法 主体      更新时间:2023-10-16

我有这个方法:

vector<float> MyObject::getResults(int n = 1000)
{
vector<float> results(n, 0);
// do some stuff
return results;
}

当然,这没有优化,我想返回这个向量的引用,但我不能简单地这样做:

const vector<float>& MyObject::getResults(int n = 1000)
{
vector<float> results(n, 0);
// do some stuff
return results;
}

这不起作用,向量将在方法结束时被销毁,因为它是一个局部变量。

所以我找到解决这个问题的唯一解决方案是在 MyObject 中创建一个私有向量并返回对该向量的引用:

const vector<float>& MyObject::getResults(int n = 1000)
{
this->results.clear();
this->results.resize(n, 0);
// do some stuff
return results;
}

这是正确的方法吗?您还有其他解决方案可以提出吗?

什么最有效?

按值返回。别担心,不会发生复制。这是最佳做法:

// Use this
vector<float> getResults(int n = 1000);

这是为什么呢?不会复制从函数返回的局部变量。它们被移动到将存储返回值的位置:

// Result moved into v; no copying occurs
vector<float> v = getResults(); 
// Result moved into memory allocated by new; no copying occurs
vector<float>* q = new vector<float>(getResults()); 

这是如何工作的?

当函数返回对象时,它以以下两种方式之一返回它:

  • 在寄存器中
  • 在内存中

您只能在寄存器中返回简单对象,如ints 和doubles。对于内存中返回的值,函数将向该函数传递一个指针,指向放置返回值所需的位置。

当您调用new vector<float>(getResults());时,会发生以下情况:

  • 计算机为新向量分配内存
  • 它给出了要getResults()的内存的位置,以及任何其他参数。
  • getResults在该内存中构造向量,无需复制。

返回对成员变量的引用怎么样?

一般来说,这是一个过早的优化可能不会提供太多或任何好处并且会使你的代码更复杂,更容易出现错误

如果将getResults的输出分配给向量,则无论如何都会复制数据:

MyObject m; 
vector<float> = m.getResults(); // if getResults returns a const reference, the data gets copied

另一方面,如果将getResults的输出分配给const reference,这可能会使管理MyObject的生命周期变得更加复杂。在下面的示例中,您返回的引用在函数结束后立即失效,因为m被销毁。

vector<float> const& evilDoNotUseThisFunction() {
MyObject m;
vector<float> const& ref = m.getResults();
return ref; // This is a bug - ref is invalid when m gets destroyed
}

对于std::vector来说,复制和移动有什么区别?

复制循环遍历向量的所有元素。复制向量时,将复制向量存储的所有数据:

vector<float> a = getVector(); // Get some vector
vector<float> b = a // Copies a

这等效于以下代码:

vector<float> a = getVector(); // Get some vector
vector<float> b(a.size()); // Allocate vector of size a
// Copy data; this is O(n)
float* data = b.data();
for(float f : a) {
*data = f;
data++;
}

移动不会循环访问任何元素。当一个向量由move构造时,就好像它被一个空向量交换了:

vector<float> a = getVector(); // Get some vector
vector<float> b = std::move(a); // Move a into b

相当于:

vector<float> a = getVector(); // Get some vector
vector<float> b; // Make empty vector (no memory allocated)
std::swap(a, b); // Swap a with b; very fast; this is O(1)

TL;DR:复制会复制循环中的所有数据。移动只是交换谁拥有内存。

我们怎么知道results被感动了?C++11 要求局部变量在返回时自动移动。您不必致电move.

掉期真的会发生吗?在许多情况下,没有。交换已经很便宜了,但编译器可以很聪明,可以完全优化交换。它通过在内存中构建您的results向量来实现这一点,它将返回results.这称为命名返回值优化。见 https://shaharmike.com/cpp/rvo/#named-return-value-optimization-nrvo

当然,

这没有优化

没事的。具体来说,从 C++11 开始,您无需在此处执行任何额外的操作。

无论如何,只有在您拥有正确的内容以及对其进行分析的方法之后,您才应该担心优化。

无论如何,返回对私有向量的引用并不理想 - 它不必要地延长了向量的生存期,并且可能会像任何其他有状态函数一样导致以后的重入问题。