转发返回值。需要std :: forward吗?

时间:2012-10-18 06:04:24

标签: c++ c++11 return rvalue-reference perfect-forwarding

我正在编写包含来自其他库的许多功能和方法的库。为了避免复制返回值,我正在应用std::forward,如下所示:

template<class T>
T&& wrapper(T&& t) { 
   f(t);  // t passed as lvalue  
   return std::forward<T>(t);
}

f返回void并获取T&&(或价值超载)。包装器总是返回包装器的参数,返回值应该保留参数的值。我是否真的需要在std::forward中使用return? RVO会让它变得多余吗?它是参考(R或L)的事实是否是多余的?如果return不是最后一个函数语句(在some if中),是否需要它?

如果wrapper()应该返回voidT&&,那么这是值得商榷的,因为调用者可以通过arg(参考,R或L)访问评估值。但在我的情况下,我需要返回值,以便wrapper()可以在表达式中使用。

这可能与问题无关,但众所周知,f函数不会从t窃取,因此在std::forward中首次使用f(std::forward<T>(t))是多余的,它被我删除了。

我写过小测试:https://gist.github.com/3910503

测试显示,返回未转发的T - 确实在gcc48中创建了额外的副本,并且在-O3中创建了clang32(RVO没有启动)。

此外,我无法在UB中获得不良行为:

auto&& tmp = wrapper(42); 

它没有证明任何原因,因为它是未定义的行为(如果它是UB)。

3 个答案:

答案 0 :(得分:9)

如果您确实知道t在调用f后不会处于移动状态,那么您的两个选择是合理的:

  • 返回类型为std::forward<T>(t)的{​​{1}},这样可以避免任何构造,但允许写入,例如T&&auto&& ref = wrapper(42);留下了悬空参考

  • 返回类型为ref的{​​{1}},当参数为右值时,最好请求移动构造 - 这可以避免prvalues的上述问题,但可能会从xvalues中窃取

在所有情况下,您都需要std::forward<T>(t)。不考虑复制省略,因为T始终是参考。

答案 1 :(得分:6)

根据此函数传递的内容,它会导致未定义的行为!更准确地说,如果将非左值(,即 rvalue)传递给此函数,则返回引用引用的值将过时。

同样T&&不是“通用参考”,但效果有点像通用参考,因为T可以推断为T&T const&。有问题的情况是它被推导为T:参数在函数返回之后作为临时传递并且在函数返回之前死亡,但在任何事情之前都可以获得对它的引用。

std::forward<T>(x)的使用仅限于在调用另一个函数时转发对象:作为临时函数的内容看起来像函数中的左值。使用std::forward<T>(x)允许x看起来像是临时的,如果它是一个 - 因此,允许在创建被调用函数的参数时从x移动。

当您从函数返回一个对象时,您可能需要处理几个场景,但它们都不涉及std::forward()

  • 如果类型实际上是引用,const或非const,则您不希望对该对象执行任何操作,只返回引用。
  • 如果所有return语句都使用相同的变量或者所有语句都使用临时变量,则可以使用复制/移动省略,并将用于正常的编译器。由于复制/移动省略是一种优化,因此不一定会发生。
  • 如果始终返回相同的局部变量或临时变量,则可以在有移动构造函数的情况下移动它,否则将复制该对象。
  • 当返回不同的变量或返回涉及表达式时,仍然可以返回引用但是复制/移动省略不起作用,如果不是临时的,也不能直接从结果中移出。在这些情况下,您需要使用std::move()来允许从本地对象移动。

在大多数情况下,生成的类型为T,您应该返回T而不是T&&。如果T是左值类型,则结果可能不是左值类型,并且可能需要从返回类型中删除引用限定。在您明确询问类型T工作的方案中。

答案 2 :(得分:3)

不,你不需要使用std::forward,最好不要返回r值参考,因为它可以阻止NRVO优化。您可以在本文中阅读有关移动语义的更多信息:Article