4

我想创建一个自定义容器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...当我声明(甚至在语法上禁止它)时,有什么方法可以避免悬空引用?

编辑 1

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

4

2 回答 2

3

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

不。从不是帮助者std::movestd::forward完全错误的东西返回右值引用。右值引用仍然是引用。返回对临时变量或局部变量的引用一直是错误的,现在仍然是错误的。这些与旧的 C++ 规则相同。

使用是否std::forward正确?我应该使用std::move(在这个例子中都可以使用)还是其他的东西?

上一个问题的答案让这个问题变得没有意义。

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

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

在这种情况下,右值引用几乎没有改变。像往常一样做事:

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

任何值得使用的编译器都会将其优化为返回值的直接初始化,而无需任何复制或移动或任何东西。对于愚蠢/毫无价值/怪异/实验性的编译器,最坏的情况是会涉及一个动作(但为什么有人会用这样的东西来处理严肃的事情呢?)。

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

总而言之,相同的旧 C++ 始终仍然有效,并且仍然给出正确和高性能的结果。请不要忘了。

于 2013-08-26T14:22:46.820 回答
3

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

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

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

于 2013-08-26T14:23:36.360 回答