【问题标题】:Difference between returning an rvalue reference and a value in && qualified functions返回右值引用和 && 限定函数中的值之间的区别
【发布时间】:2017-07-14 18:49:33
【问题描述】:

我最近在Is returning by rvalue reference more efficient? 看到了这个问题,也注意到了 cmets 链。我正在跟进 cmets 链以获得那里的答案。

人们似乎说,在下面的情况下,返回值应该是右值引用而不是值。 (Is returning by rvalue reference more efficient?Is there any case where a return of a RValue Reference (&&) is useful?)给出以下代码

#include <iostream>

using namespace std;

class Something {
public:
    Something() {
        cout << "Something()" << endl;
    }
    Something(const Something&) {
        cout << "Something(const Something&)" << endl;
    }
    Something(Something&&) {
        cout << "Something(Something&&)" << endl;
    }
};

class Maker {
public:
    Something get_something() && {
        return std::move(this->something);
    }
    const Something& get_something() & {
        return this->something;
    }

private:
    Something something;
};

int main() {
    auto maker = Maker{};
    auto something_one = maker.get_something();
    auto something_two = Maker{}.get_something();

    return 0;
}

用右值引用返回类型和常规值定义类Maker 中的第一个方法有什么区别?运行上面的代码会按预期给我以下输出(移动发生在函数调用中,返回后移动被省略)

Something()
Something(const Something&)
Something()
Something(Something&&)

当我更改代码(即Maker 类中的第一个方法)以返回右值引用时,我仍然得到相同的输出。在这种情况下,为什么我不应该按值返回?

【问题讨论】:

  • 目前还不清楚这里有什么问题。为什么你认为在这种情况下你不应该按值返回?
  • @NicolBolas 更新了问题。我想知道为什么人们似乎说这是通过右值引用返回的一个很好的用例。我正在努力寻找通过右值引用返回的好用例..
  • 我在这些问题中看到没有人说它“应该”在那里使用。只是它可以这样使用。这完全取决于你想要做什么。
  • @NicolBolas 是否有任何用例无法通过按值返回更好地服务?
  • 另一张海报是正确的;与我最初使用的相比,它更像是 Return value or rvalue reference 的副本。

标签: c++ c++11 return-value rvalue return-value-optimization


【解决方案1】:

当你以这种方式使用this 引用限定符时,你必须问自己两个问题:

  1. std::move(object).funcname() 是什么意思?
  2. Typename().funcname() 是什么意思?

如果你从函数中返回一个值,那么这两者的含义是一样的。无论您如何捕获该值,该值都将是一个完整且不同的对象,由存储在该对象中的一些内部数据移动构造而成。

在第一种情况下,object 现在可能不再拥有数据。在第二种情况下,这无关紧要,因为该对象是临时对象,并且已被销毁。

如果你从函数中返回一个&amp;&amp;,那么这些将意味着不同的东西。即,#2 表示“您的代码已损坏”。

至于为什么您可能仍然想要这样做,即使它允许损坏代码。嗯,这与该问题的实际答案有关:这些东西 意味着什么

这就是我所指的。 std::get 基本上是tuple 的成员函数。然而,如果你传递一个tuple&amp;&amp;,你将得到一个T&amp;&amp;,而不是T。为什么?

因为您正在访问tuple成员

如果您有一个struct,那么std::move(struct_object).x 也将是一个右值引用。所以std::gettuple 的行为方式与struct 的成员访问方式相同。这就是tuple 的重点,毕竟:尽可能表现得像struct

这允许您执行std::get&lt;0&gt;(std::move(tpl)).member 之类的操作,这样member 仍将是一个右值引用。因此,您可以从子对象中移动而不会干扰对象的 rest,就像对任何其他右值引用成员访问一样。

如果get 返回一个值,那么这将做一些非常不同的事情。无论我们如何处理返回值,它都会被移出对象,不问任何问题。因此,如果我们只想移动该成员的子对象……太糟糕了。原始对象丢失了整个成员,而不仅仅是该成员的子对象。

当然,这并不能改变以下事实:

auto &&x = SomeStruct().x; //This extends the temporary's lifetime
auto &&x = std::get<0>(SomeTuple(...)); //This gets a dangling reference.

这是语言的不幸限制。但是,如果该函数在逻辑上是成员访问器,则从 &amp;&amp; 限定的 this 函数返回 &amp;&amp; 而不是值是合理的选择。

这一切都取决于对您来说更重要的是:安全性还是与成员访问者的正交性。

【讨论】:

  • 感谢您的回答!再来一波。 std::move(SomeStruct()).x; 我知道这是一个右值,但它是一个 xvalue 还是一个 prvalue?我不认为它是一个prvalue,因为它有一个名字,但是为什么它的生命周期会延长呢?我们永远不知道何时延长 xvalue 的生命周期,对吧?
  • 根据en.cppreference.com/w/cpp/language/value_category 成员对任何类型的右值(xvalues 或 prvalues)的访问似乎都是纯右值。但我还是想确定我没有误会……
  • @Curious: "对任何类型的右值的成员访问都是纯右值" 它没有这么说。总是产生纯右值的成员访问要求m 是“成员枚举器或非静态成员函数”。我们谈论的是非静态 data 成员。如果 a 是右值,m 是 NSDM,那么 a.m 是 xvalue,就像那个网站说的那样。
  • @Curious:“为什么它的生命周期会延长?”实际上......它不是。 std::move 不应该在那里。它破坏了延长寿命的能力。我已经相应地编辑了示例。
  • 好的,谢谢!所以只是为了澄清。不是正确的prvalue吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多