【问题标题】:How to return the reference of a declared vector in the method body?如何在方法体中返回已声明向量的引用?
【发布时间】:2019-11-26 16:45:03
【问题描述】:

我有这个方法:

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;
}

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

【问题讨论】:

  • 使向量成为成员变量。
  • 复制返回有什么问题? NRVO 会解决这个问题。
  • 这已经是我提出的解决方案,但我看看是否有更好的解决方案。此外,清理和调整我的矢量比重新创建一个新矢量需要更多时间。
  • 我不知道 NRVO 是如何工作的。我会询问这件事。谢谢。
  • “当然这不是优化的”它具体!如前所述,请查看 NRVO。

标签: c++ pointers reference pass-by-reference


【解决方案1】:

什么是最有效的?

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

// 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&lt;float&gt;(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

【讨论】:

  • 好的,非常感谢,只是一个简单的问题。 复制的移动的有什么区别?
  • 英文copy和move有什么区别?一个制作副本(然后在这种情况下破坏原始文件),另一个......将存储从一个变量移动到另一个变量而不进行复制。具体使用move constructor 而不是copy constructor
  • 我添加了一个解释差异的部分!
  • 旁注:交换是一种可能的实现,但不是必需的。移出的向量处于未指定但有效的状态。例如,GCC 会删除移动到的向量的先前内存,将移动到的向量保留为空(没有容量)...
【解决方案2】:

当然这不是优化的

没关系。具体来说,从 C++11 开始,您不需要在这里做任何额外的事情。

在任何情况下,只有在您拥有正确的东西以及对其进行分析的方法之后,您才应该担心优化。

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

【讨论】:

    猜你喜欢
    • 2013-02-02
    • 1970-01-01
    • 2021-12-29
    • 2017-12-29
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多