右值系列之六:向前,向前!

原文来自: http://cpp-next.com/archive/2009/12/onward-forward/

除了提供转移语义,右值引用的另一个主要用途是解决“完美转发”。在这里,“转发”的指将一个泛型函数的实参转发至另一个函数而不会拒绝掉第二个参数可接受的任何参数,也不会丢失关于这些参数的cv限定或左右值属性的任何信息,而且还无须采用重载。在C++03中,最佳的近似是将所有右值变为左值,并且需要两个重载。

为什么要解决这个问题?

考虑以下例子:

template <class F>
struct unary_function_wrapper
{
     unary_function_wrapper(F f) : f(f) {}
 
     template <class ArgumentType>
     void operator()( ArgumentType x ) { f(x); }
 private:
     F f;
};

这种方式是不行的,因为我们的函数调用操作符是传值的,它会拒绝所有不可复制/不可转移的类型,即便 f 可以接受这些类型。如果我们把它改为:

template <class ArgumentType>
void operator()( ArgumentType& x ) { f(x); }

那么我们会拒绝所有非常量性的右值。我们可以加一个重载:

template <class ArgumentType>
void operator()( ArgumentType& x ) { f(x); }
template <class ArgumentType>
void operator()( ArgumentType const& x ) { f(x); }

但是这也和前面的一样,会丢失右值性,而我们本想保留它的,以便 f 可以利用那些我们在前面几篇文章中讨论过的转移优化。引入第二个重载还会带来另一个问题:它不能扩展至多个参数的情形。一个 binary_function_wrapper 就需要四个重载,再来一个 ternary_function_wrapper 就需要八个重载了,完美转发 n 个参数需要 2^n 个重载。

可行的解决方案

对于右值引用,我们可以利用一些特殊设计的语言规则来解决这个问题:

template <class ArgumentType>
void operator()( ArgumentType && x )
{ f( std::forward<ArgumentType>(x) ); }

这两个特殊规则是:

  1. 关于右值引用消除的规则。在C++0x中,很早以前就决定了如果 T 为 U&,则 T& 也为 U&。这是左值引用消除。而对于右值引用,规则被更改为:
    1. & + & 变为 &
    2. & + && 变为 &
    3. && + & 变为 &
    4. && + && 变为 &&

    即,任一”左值性”都会令结果变为左值。

  2. 关于推定“全泛化右值引用”参数(如上例中的 ArgumentType)的规则。该规则规定,如果实参是一个右值,则 ArgumentType 被推定为非引用类型,而如果实参是一个左值,则 ArgumentType 则被推定为左值引用类型。

当实参为类型 Y 的右值时,这两个规则的结果是:ArgumentType 被推定为 Y,因此这里只有一个引用且没有被消除:函数的参数类型为 Y&&。

当实参是类型 Y 的左值时,ArgumentType 被推定为 Y& (或 Y const&),且引用消除规则会起作用,使得实例化的函数的参数类型为 Y& && 即 Y&...它刚好被绑定到一个左值。

最后一个要点是 forward 函数,它的任务是,当 ArgumentType 为非引用时,“重建”实参的右值性,从而无干扰地把它传递给 f。

和 std::move 一样,std::forward 是一个零开销的操作。虽然它不是一个真正的转换,但是你可以把 std::forward<ArgumentType>(x) 想象为 static_cast<ArgumentType&&>(x) 的描述性表示:当实参是一个右值时,我们把 x 转型为一个匿名右值引用,但是当实参是一个左值时,ArgumentType 就是一个左值引用且引用消除会起作用,因此 static_cast 的结果仍是一个左值引用。

“forward” 实际上意味着什么?

最近,关于 forward 的定义是否应该进行调整以适用于除“完美转发”以外的用途,产生了一些实质性的异议,这些其它用途包括:用于类似于 std::tuple 这样的类型的转移构造函数,这些类型可能含用引用成员,并且要防止将左值引用绑定到右值成员这样的危险情形。被建议的调整通常被称为帮助你“把一个 X 当作一个 Y 来转发”。有关这些调整的细节,请参考 N2951

我从来没有认同过这个方向的调整,因为“转发一个函数的实参并保持它们的cv限定及右值性”对我来说非常有意义,而“把 X 当作 Y 来转发”则没有明显的意义。换句话说,这种 forward 如何使用以及为何要用,并没有明显的心理模型:我们没有与之对应的编程模型。我最终放弃对此的抵抗,这是因为我看到,它在某些用户必须要处理的与类相关的一些问题方面是有用的,但是我仍然认为,我们必须指出它的意义所在,并且把它解释清楚。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值