如何在C ++中使用rvalue引用来避免不必要的实例

时间:2013-08-26 14:18:28

标签: c++ c++11 rvalue-reference

我想创建一个自定义容器Container,用于在单个数组中存储数据。但是,为了便于对容器进行迭代,我通过重载operator[]在容器上提供了一个“视图”,并返回一个结构Value,它将所有容器变量保存为实际容器的引用。这是我到目前为止所得到的:

#include <iostream>
using namespace std;

struct Value {
  Value(int& data) : data_(data) { }
  int& data() { return data_; }
  int& data_;
};

struct Container {
  Value makeValue(int i) { return Value(data_[i]); } // EDIT 1
  Value&& operator[](int i) {
    // return std::forward<Value>(Value(data_[i]));
    return std::forward<Value>(makeValue(i)); // EDIT 1
  }

  int data_[5] = {1, 2, 3, 4, 5};
};

int main(int, char**)
{
  // Create and output temporary
  Container c;
  cout << c[2].data() << endl; // Output: 3 - OK!

  // Create, modify and output copy
  Value v = c[2];
  cout << v.data() << endl; // Output: 3 - OK!
  v.data() = 8;
  cout << v.data() << endl; // Output: 8 - OK!

  // Create and output reference
  Value&& vv = c[2];
  cout << vv.data() << endl; // Output: 8 - OK, but weird:
                             // shouldn't this be a dangling reference?
  cout << vv.data() << endl; // Output: 468319288 - Bad, but that's expected...
}

上面的代码据我所知,但我想知道我是否使用了最好的方法:

  1. 如果我想避免不必要的复制,将Value作为右值引用返回是否正确?
  2. 使用std::forward是否正确?我应该使用std::move(两者都适用于此示例)还是其他什么?
  3. 编译程序的输出在注释中说明。当我宣布Value&& vv...(或甚至在语法上禁止它)时,有什么办法可以避免悬空参考吗?
  4. 编辑1

    我对源代码做了一些小改动,以便Value实例不是直接在operator[]方法中创建,而是在另一个辅助函数中创建。这会改变什么吗?我应该使用所示的makeValue(int i)方法,还是需要在此使用std::move / std::forward

2 个答案:

答案 0 :(得分:3)

  

如果我想避免不必要的复制,将Value作为右值引用返回是否正确?

没有。从不是std::movestd::forward的帮助者的方式返回rvalue引用是完全错误的。 Rvalue引用仍然是引用。返回对临时变量或局部变量的引用总是错误的,但仍然是错误的。这些是旧的C ++规则。

  

使用std::forward是否正确?我应该使用std::move(两者都可以在这个例子中使用)还是别的什么?

上一个问题的答案有点让人无动于衷。

  

编译程序的输出在注释中说明。当我宣布Value&& vv...(或甚至在句法上禁止它)时,有什么方法可以避免悬空参考?

创建悬空参考不是Value&& vv = c[2];部分。这是operator[]本身:见第一个问题的答案。

在这种情况下,Rvalue引用几乎没有变化。按照你一直以来的方式做事:

Value operator[](int i) {
    return Value(data_[i]);
}

任何值得使用的编译器都会将其优化为返回值的直接初始化,而无需任何副本或移动或任何内容。对于愚蠢/无价值/怪异/实验编译器,它最坏的情况是移动(但为什么有人会使用这样的东西来处理严重的事情?)。

因此,行Value v = c[2];将直接初始化v。行Value&& vv = c[2];将初始化临时值并将其绑定到右值引用变量。它们具有与const&相同的属性,并且它们将临时的生命周期延长到引用的生命周期,因此它不会悬空。

总之,相同的旧C ++仍然有效,并且仍然提供正确且高效的结果。不要忘记它。

答案 1 :(得分:3)

返回对临时对象的引用,即使它是r值引用,也总是错误的!当您访问该对象时,它将消失。在这种情况下,它也不会做你想做的事情,无论如何:如果你想避免不必要的副本,请让一个return语句返回一个临时的!复制/移动elision将处理未被复制的对象:

Value operator[](int i) {
    return Value(data_[i]);
}

通过函数传递临时对象将禁止复制/移动省略,而不是复制/移动比移动更少工作。