是否有任何理由将 return 值捕获为右值引用?

Is there any reason to capture a return-value as an rvalue-reference?

我有一个只能移动的结构 Foo 和一个函数 Foo get();

如果我想以可变方式捕获 get 的 return 值,我有两个选择:

当我通过右值引用捕获时,我创建了一个左值,很像通过值捕获。

我很难看出不同选项之间的意义?

工作示例:

#include <iostream>

struct Foo
{
    Foo(std::string s) : s(std::move(s)) {}
    Foo(Foo&& f)       : s(std::move(f.s)) {}

    Foo(const Foo&) = delete;
    Foo& operator=(const Foo&) = delete;

    std::string s;
};

Foo get()
{
    return Foo { "hello" };
}

int main()
{
    // capture return value as l-value
    Foo lv1 = get();

    // move into another lvalue
    Foo lv2 = std::move(lv1);
    std::cout << lv2.s << '\n';

    // capture return value as r-value reference
    Foo&& rv1 = get();

    // move into another lvalue
    Foo lv3 = std::move(rv1);
    std::cout << lv3.s << '\n';

    return 0;
}
Foo lv1 = get();

这要求 Foo 是 copy/moveable。

Foo&& rv1 = get();

这不是(至少,就这行代码而言不是;get 的实现可能仍然需要一个)。

即使允许编译器将 return 值的副本省略到变量中,这种形式的副本初始化仍然需要存在可访问的副本或移动构造函数。

因此,如果您想对类型 Foo 施加尽可能少的限制,您可以存储 returned 值的 &&

当然,C++17 改变了这个规则,所以第一个不需要 copy/move 构造函数。

Foo&&

这将创建对临时值的引用(或存储分配给它的右值引用)。如果它存储对临时值的引用,则其生命周期会延长该值。

Foo

无论 returned 是什么,这都会存储值的副本。

在C++11和14中,如果Foo不能移动,将Foo make_foo()赋值给Foo类型的变量是非法的。那里有一个移动,即使移动被省略(并且 return 值和外部范围内的值合并了生命周期)。

在 C++17 中,保证省略意味着不需要存在移动构造函数。

Foo x = make_foo(); // Foo make_foo()

C++17 中的上述保证 make_foo() 的 return 值只是命名为 x。其实临时withinmake_foo也可能是x;同一个对象,不同的名字。无需移动。

还有其他一些细微差别。 decltype(x) 将 return 声明的类型 x;所以 FooFoo&& 取决于。

另一个重要区别是它与 auto.

一起使用
auto&& x = some_function();

这会创建对任何内容的引用。如果 some_function return 是临时的,它会绑定一个右值引用到它并延长它的生命周期。如果它 return 是引用,x 匹配引用的类型。

auto x = some_function();

这会创建一个值,可以从 some_function return 中复制,或者可以用 some_function 的 return 值省略,如果它 return是临时的。

auto&&在某种意义上意味着"just make it work, and don't do extra work",它可以推导出Foo&&auto 表示 "store a copy".

在 "almost always auto" 风格中,这些比明确的 FooFoo&&.

更常见

auto&& 永远不会推导为 Foo,但可以推导为 Foo&&.

auto&& 的最常见用途,即使在几乎总是 auto 之外,也是:

for(auto&& x : range)

其中 x 成为遍历范围的有效方式,我们不关心 range 有多少类型。另一个常见的用法是:

[](auto&& x){ /* some code */ }

lambdas 通常用于类型很明显且不值得再次键入的上下文中,例如传递给算法等。通过使用 auto&& 作为参数类型,我们使代码不那么冗长。