在 && 限定函数中返回右值引用和值之间的区别

Difference between returning an rvalue reference and a value in && qualified functions

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

人们似乎说在下面的情况下 return 值应该是右值引用而不是值。 (Is returning by rvalue reference more efficient?RValue Reference (&&) 的 return 是否有用?)给定以下代码

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

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

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

并且当我将代码(即 Maker class 中的第一个方法)更改为 return 右值引用时,我仍然得到相同的输出。在那种情况下,我为什么不按值 return 呢?

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

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

如果您 return 函数中的一个值,那么这两个值将意味着相同的事情。无论您如何捕获该值,该值都将是一个完整且不同的对象,由存储在该对象中的一些内部数据移动构造而成。

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

如果你从函数中 return 一个 &&,那么它们将意味着不同的东西。即,#2 表示 "your code is broken".

至于为什么你可能仍然想这样做,即使它允许破坏代码。好吧,这与该问题的实际答案有关:这些东西是什么意思 ?

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

因为您正在访问 tuple 成员

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

这允许你做类似 std::get<0>(std::move(tpl)).member 的事情,这样 member 仍然是一个右值引用。因此,您可以在不干扰对象的 rest 的情况下从子对象移动,就像您对任何其他右值引用成员访问所做的那样。

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

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

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

不幸的是,这是语言的局限性。但是如果该函数在逻辑上是一个成员访问器,那么 return 从一个 && 限定的 this 函数而不是一个值中 && 是一个合法的选择。

这完全取决于什么对您更重要:安全性或与成员访问器的正交性。