【问题标题】:Using of rvalue references in c++11在 c++11 中使用右值引用
【发布时间】:2012-03-28 19:43:37
【问题描述】:

我想实现一个填充向量然后返回右值引用的函数。我厌倦了这样的事情:

std::vector<int> &&fill_list() {
  std::vector<int> res;
  ... do something to fill res ...
  return res;
}

int main(int argc, char **argv) {
  std::vector<int> myvec = fill_list();
  return 0;
}

但这不起作用,我收到以下错误:

error: invalid initialization of reference of type 'std::vector<int>&&' from expression of type 'std::vector<int>'

那么,总而言之,正确的做法是什么?我认为我还没有得到右值引用。

【问题讨论】:

  • 即使编译成功,它也会返回一个悬空引用。 为什么你想返回一个右值引用?为什么不简单地按值返回?
  • 我认为你对右值引用的理解是错误的:它是一个无论如何都会被破坏的 value 可以移动而不是需要复制它。如果你直接返回一个右值引用,那么不再有值了,因为它在在你有机会移动它的内容之前已经被销毁了。
  • @Sambatyon :如果您按值返回,则将使用 then 移动语义(如果 NRVO 没有启动)。您显示的确实是一个悬空引用,就像您的返回类型是 std::vector&lt;int&gt;&amp; 一样。
  • @Sambatyon: idljarn 是正确的,返回一个 rvalue-reference 和一个 lvalue-reference 在这里没有区别,引用将指向一个对象刚刚被销毁。

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


【解决方案1】:

您似乎对什么是右值引用以及它与移动语义的关系感到困惑。

第一件事是第一:&amp;&amp; 并不意味着移动。它只不过是一种特殊的引用类型。 仍然是一个参考。它不是一个值;它不是一个 移动 值;它是一个值的引用。这意味着它具有引用类型的所有限制。值得注意的是,它必须引用一个仍然存在的值。所以返回一个悬空的右值引用并不比返回一个悬空的左值引用好。

“移动”是让一个对象声称拥有另一个对象的内容的过程。 R 值引用促进 移动语义,但仅仅拥有&amp;&amp; 并不意味着任何东西已经移动。 仅在调用移动构造函数(或移动赋值运算符)时发生移动;除非这两个东西之一被调用,否则不会发生任何运动。

如果您希望将 std::vector 的内容从您的函数中移出给用户,您只需执行以下操作:

std::vector<int> fill_list() {
  std::vector<int> res;
  ... do something to fill res ...
  return res;
}

鉴于fill_list()的这种用法:

std::vector<int> myvec = fill_list();

会发生两种情况之一。返回将被忽略,这意味着不会发生复制或移动。 res 直接构造成myvec。或者res 将被移动到返回值中,然后将执行myvec 的移动初始化。再说一遍,不要抄袭。

如果你有这个:

std::vector<int> myvec;
myvec = fill_list();

再一次,它将被移入。不得抄袭。

C++11 知道什么时候隐式移动是安全的。按值而不是按引用或某事返回值始终是移动的安全时间。因此,它会移动。

【讨论】:

  • 遗憾的是,C++11 对何时尝试实际移动局部变量非常严格。它只允许编译器在复制省略的条件适用的情况下这样做,但无论出于何种原因都不能执行复制省略。这太严格了,但在这个标准中无济于事。
【解决方案2】:

关于右值引用的讨论,您可以阅读 C++ 的作者 Bjarne Stroustrup 在这里所说的:

http://www2.research.att.com/~bs/C++0xFAQ.html#rval

针对您的具体示例,简短的回答是由于 命名返回值优化 - 这是事实上的标准 C++ 编译器功能,甚至是 C++11 之前的 - 如果您只是返回 -按值编译器会像你想要的那样有效地绑定 res 和 myvec:

std::vector<int> fill_list() {
    std::vector<int> res;
    ... do something to fill res ...
    cout << &res << endl; // PRINT POINTER
    return res;
}

int main(int argc, char **argv) {
     std::vector<int> myvec = fill_list();
     cout << &myvec << endl; // PRINT POINTER
     return 0;
}

在这两种情况下,两个“PRINT POINTER”行将打印相同的指针。

上面的向量 myvec 和向量 res 将是同一个向量,具有相同的存储空间。不会调用复制构造函数或移动构造函数。从某种意义上说 res 和 myvec 将是同一个对象的两个别名。

这甚至比使用移动构造函数更好。 myvec 是在原地构建和“填充”的。

编译器通过在“就地”模式下编译函数来实现这一点,将调用者堆栈帧中的立即堆栈位置与被调用者本地结果变量重叠,并在被调用者返回后简单地将其保留在那里。

在这种情况下,我们说构造函数已被省略。有关更多信息,请参见此处:

http://en.wikipedia.org/wiki/Return_value_optimization

如果您在非构造函数上下文中分配 fill_list 的结果,则按值返回会产生 xvalue(“过期”值的缩写,一种类型的右值),如果目标变量的移动赋值运算符可用,将优先考虑它。

【讨论】:

  • "在这种情况下,我们说复制结构已被省略。"请注意,这也适用于移动构造。
  • RVO 在这种情况下是一个红鲱鱼。编写可以从中受益的代码是好建议,但这无助于理解手头的问题。
  • "std::move 和 std::forward 仅在右值和左值参数之间存在潜在的重载决议时才相关" 嗯?你是从哪里得到这个想法的?这根本没有意义。无论如何,我没有投反对票,所以不需要安抚我。 ;-] (此外,OP 一开始就感到困惑,因此首先提出了这个问题;他用绿色勾号标记了您的答案并不会使其更相关或更正确。)
  • "std::move 和 std::forward 除了修改选择的重载之外没有任何用处。" 显然是错误的——我会断言 you 不理解右值引用。 std::move 将左值转换为右值,std::forward 防止右值在完美转发期间错误地衰减为左值。毕竟,你的固执确实诱使我投反对票……帮自己一个忙,去阅读this answer。
  • 我不会使用std::move 或std::forward 来改变临时对象的生命周期,我也没有说过我会这样做。让我们为您简化这所幼儿园:1) std::move 和 std::forward 影响左值或右值。 2) Lvalue-ness 或 rvalue-ness 影响语义超出选择的重载。 3)“std::move 和 std::forward 除了修改选择的重载之外没有其他用处。”因此充其量只是过于简单化,但实际上是错误的。享受你的“绿勾”吧,在 SO 上,无知不会经常胜出。
【解决方案3】:

return 语句是错误的,因为您尝试将右值引用(返回类型)绑定到左值(向量 res)。右值引用只能绑定到右值。

另外,正如其他人已经提到的,当返回类型是引用类型时返回局部变量是危险的,因为局部对象会在 return 语句之后被销毁,然后你得到一个引用无效对象的引用。

如果您想避免在 return 语句期间进行复制构造,由于称为复制省略的功能,仅使用非引用类型可能已经有效。 fill_list 函数中的向量 res 可以直接构造到 main 函数中的向量 myvec 中,因此根本不会调用复制或移动构造。但是这个特性是标准允许的,不是必需的,一些编译器没有省略一些复制构造。

【讨论】:

    【解决方案4】:

    如果你只是从你的函数中删除&amp;&amp; 它应该可以工作,但它不会是一个参考。 fill_list() 将创建一个向量并返回它。在返回期间将创建一个新向量。在 fill_list() 中创建的第一个向量将被复制到新向量,然后将被销毁。这是复制构造函数的工作。

    【讨论】:

    • 如果 this 按值返回,它将使用 move 构造函数,而不是复制构造函数。
    • 实际上,它根本不需要使用任何构造函数,因为现代编译器可以对返回值进行复制省略。
    • 不知道。事实上,他们是时候这样做了! :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-27
    • 2016-06-10
    • 1970-01-01
    • 2013-08-07
    相关资源
    最近更新 更多