我有一个仅移动的结构Foo
和一个函数Foo get();
如果我想以可变的方式捕获get
的返回值,我有两个选择:
Foo
)Foo&&
)当我通过rvalue-reference捕获时,我创建一个左值,就像按值捕获一样。
我很难看到不同选项之间的关系?
工作示例:
#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;
}
答案 0 :(得分:4)
Foo lv1 = get();
这要求Foo
是可复制/可移动的。
Foo&& rv1 = get();
这不是(至少不是这行代码; get
的实现可能仍然需要一个)。
即使允许编译器将返回值的副本遗漏到变量中,此表单的复制初始化仍然需要存在可访问的副本或移动构造函数。
因此,如果您希望对类型Foo
施加尽可能少的限制,则可以存储返回值的&&
。
当然,C ++ 17会更改此规则,以便第一个不需要复制/移动构造函数。
答案 1 :(得分:3)
Foo&&
这将创建对临时值的引用(或存储分配给它的右值引用)。如果它存储对临时值的引用,则其生命周期会扩展该值。
Foo
无论返回什么内容,都会存储该值的副本。
在C ++ 11和14中,如果无法移动Foo
,则将Foo make_foo()
分配给Foo
类型的变量是非法的。在那里有一个移动,即使移动被省略(并且返回值和外部范围中的值已经合并了生命周期)。
在C ++ 17中,保证省略意味着不需要移动构造函数。
Foo x = make_foo(); // Foo make_foo()
上面的C ++ 17保证make_foo()
的返回值只是名为x
。事实上, make_foo
中的临时也可能是x
;同一个对象,名称不同。不需要移动。
还有一些其他微妙的差异。 decltype(x)
将返回声明的x
类型;因此Foo
或Foo&&
取决于。
另一个重要区别是它与auto
一起使用。
auto&& x = some_function();
这会创建对任何内容的引用。如果some_function
返回临时值,它会绑定对它的右值引用并延长其生命周期。如果它返回引用,x
匹配引用的类型。
auto x = some_function();
这会创建一个值,该值可以从some_function
返回的内容中复制,如果返回临时值,则可以使用some_function
的返回值进行删除。
auto&&
意味着&#34;只是让它发挥作用,而不是做额外的工作&#34;,它可以演绎为Foo&&
。 auto
表示&#34;存储副本&#34;。
在&#34;几乎总是自动&#34;风格,这些比明确的Foo
或Foo&&
更常见。
auto&&
永远不会推断为Foo
,但可以推断为Foo&&
。
auto&&
的最常见用途,即使在几乎总是自动之外,也是:
for(auto&& x : range)
其中x
成为迭代范围的有效方式,我们不关心range
类型的内容。另一个常见用途是:
[](auto&& x){ /* some code */ }
lambda通常用于类型明显且不值得再次输入的上下文中,例如传递给算法等。通过将auto&&
用于参数类型,我们可以使代码更简洁。