是否有任何理由将 return 值捕获为右值引用?
Is there any reason to capture a return-value as an rvalue-reference?
我有一个只能移动的结构 Foo
和一个函数 Foo get();
如果我想以可变方式捕获 get
的 return 值,我有两个选择:
- 按值 (
Foo
)
- 通过右值引用 (
Foo&&
)
当我通过右值引用捕获时,我创建了一个左值,很像通过值捕获。
我很难看出不同选项之间的意义?
- 这两者有区别吗?
工作示例:
#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
;所以 Foo
或 Foo&&
取决于。
另一个重要区别是它与 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" 风格中,这些比明确的 Foo
或 Foo&&
.
更常见
auto&&
永远不会推导为 Foo
,但可以推导为 Foo&&
.
auto&&
的最常见用途,即使在几乎总是 auto 之外,也是:
for(auto&& x : range)
其中 x
成为遍历范围的有效方式,我们不关心 range
有多少类型。另一个常见的用法是:
[](auto&& x){ /* some code */ }
lambdas 通常用于类型很明显且不值得再次键入的上下文中,例如传递给算法等。通过使用 auto&&
作为参数类型,我们使代码不那么冗长。
我有一个只能移动的结构 Foo
和一个函数 Foo get();
如果我想以可变方式捕获 get
的 return 值,我有两个选择:
- 按值 (
Foo
) - 通过右值引用 (
Foo&&
)
当我通过右值引用捕获时,我创建了一个左值,很像通过值捕获。
我很难看出不同选项之间的意义?
- 这两者有区别吗?
工作示例:
#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
;所以 Foo
或 Foo&&
取决于。
另一个重要区别是它与 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" 风格中,这些比明确的 Foo
或 Foo&&
.
auto&&
永远不会推导为 Foo
,但可以推导为 Foo&&
.
auto&&
的最常见用途,即使在几乎总是 auto 之外,也是:
for(auto&& x : range)
其中 x
成为遍历范围的有效方式,我们不关心 range
有多少类型。另一个常见的用法是:
[](auto&& x){ /* some code */ }
lambdas 通常用于类型很明显且不值得再次键入的上下文中,例如传递给算法等。通过使用 auto&&
作为参数类型,我们使代码不那么冗长。