【发布时间】:2015-09-30 07:52:16
【问题描述】:
如果您查看get,std::tuple 的辅助函数,您会注意到以下重载:
template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&&
get( tuple<Types...>&& t );
换句话说,当输入元组本身是一个右值引用时,它返回一个右值引用。为什么不按值返回,在函数体中调用move?我的论点如下: get 的返回将被绑定到一个引用或一个值(我想它可能没有绑定,但这不应该是一个常见的用例)。如果它绑定到一个值,那么无论如何都会发生移动构造。因此,按值返回不会有任何损失。如果绑定到引用,则返回右值引用实际上可能是不安全的。举个例子:
struct Hello {
Hello() {
std::cerr << "Constructed at : " << this << std::endl;
}
~Hello() {
std::cerr << "Destructed at : " << this << std::endl;
}
double m_double;
};
struct foo {
Hello m_hello;
Hello && get() && { return std::move(m_hello); }
};
int main() {
const Hello & x = foo().get();
std::cerr << x.m_double;
}
运行时,该程序会打印:
Constructed at : 0x7ffc0e12cdc0
Destructed at : 0x7ffc0e12cdc0
0
换句话说,x 立即是一个悬空引用。而如果你只是这样写 foo:
struct foo {
Hello m_hello;
Hello get() && { return std::move(m_hello); }
};
不会发生此问题。此外,如果您像这样使用 foo:
Hello x(foo().get());
无论您是按值返回还是按右值引用返回,似乎都没有任何额外的开销。我已经测试过这样的代码,似乎它会始终只执行一次移动构造。例如。如果我添加一个成员:
Hello(Hello && ) { std::cerr << "Moved" << std::endl; }
并且我按照上面的方法构造 x,我的程序只打印一次“已移动”,无论我是按值返回还是按右值引用返回。
我失踪是否有充分的理由,或者这是一个疏忽?
注意:这里有一个很好的相关问题:Return value or rvalue reference?。似乎说在这种情况下价值回报通常更可取,但它出现在 STL 中的事实让我很好奇 STL 是否忽略了这个推理,或者他们是否有自己的特殊原因可能不适用一般。
编辑:有人建议此问题与Is there any case where a return of a RValue Reference (&&) is useful? 重复。不是这种情况;这个答案建议通过右值引用返回作为省略数据成员复制的一种方式。正如我在上面详细讨论的那样,如果您先调用move,则无论您是按值返回还是按右值引用返回,复制都将被省略。
【问题讨论】:
-
这不是远程复制。我的回答明确而详细地讨论了按值返回和预先调用 std::move 如何达到从数据成员移动的相同效果。我的问题与两种方法的差异有关。我自己引用的问题与我的问题比您引用的问题更相关。在匆忙标记重复内容之前,请仔细阅读。
-
如果您按值返回 - 您将创建一个副本。如果你移动它,你移动一个副本。如果你返回一个右值引用,你可以移动它,然后移动原始对象,或者不使用移动语义处理它,那么它的行为就像常规引用一样。关键是如果你返回一个右值引用,你不会强制复制。
-
为什么需要移动可施工性?
-
@DavidHaim 您无需创建副本,因为您可以自行验证。当您按值返回但将返回转换为右值时,它会移动构造返回。
-
@Columbo 这是一个有趣的案例。但是,如果包含的类型不可移动,则元组将不会移动,而且您甚至不太可能首先获得对元组的右值引用。那么我心中的问题是,这是否超过了悬空引用的危险?
标签: c++ c++11 stl move-semantics