C++11完美转发

C++11 完美转发

简介

该篇博客主要介绍C++11中的完美转发,对完美转发的原理和使用方法进行介绍。

完美转发

完美转发的定义

所谓完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。比如:

template <typename T> 
void IamForwarding(T t) {  IrunCodeActually(t); }

在上面的例子中,IamForwarding的参数中使用了最基本类型进行转发,该方法会导致参数在传给IrunCodeActually之前就产生了一次额外的临时对象拷贝。这样的转发只能说是正确的转发,但谈不上完美,我们希望的是IamForwarding能够将参数按照传入时的类型传递给IrunCodeActually

使用“万能”引用

再看下面的代码,尝试使用“万能”的常量左值类型,但是常量左值为参数的转发函数会有一些尴尬:

void IrunCodeActually(int i) { }
template <typename T> 
void IamForwarding(const T& t) {  IrunCodeActually(t); }

大家可以明显看出其中的缺陷,由于目标函数的参数类型是非常量左值引用类型,因此无法接收常量左值引用作为参数。虽然使用“万能”引用提高了转发函数的接受能力,但在目标函数的接受上却出了问题。虽然可以通过一些常量和非常量的重载来解决目标函数的接受问题,但若函数参数比较多的情况下,就会造成代码冗余。并且若目标函数的参数是个右值引用,同样无法接受任何左值类型作为参数,也就无法使用移动语义。

引用折叠规则

在C++11以前,形如下列语句:

typedef const int T;
typedef T& TR;
TR& v = 1;             // 该声明在C++98中会导致编译错误

其中TR& v = 1这样的表达式会被编译器认为是不合法的表达式,而在C++11中,一旦出现了这样的表达式,就会发生引用折叠。C++11中的引用折叠规则如下表所示:

TR的类型定义 声明v的类型 v的实际类型
T& TR T&
T& TR& T&
T& TR&& T&
T&& TR T&&
T&& TR& T&
T&& TR&& T&&

为利用上述折叠规则,我们将转发函数写成如下形式:

template <typename T>
void IamForwarding(T&& t) {
	IrunCodeActually(static_cast<T &&>(t));
}

上述转发函数可以实现完美转发,但当传入左值引用时,会觉得static_cast多余,但在此处,它就是用于传入一个右值引用时发挥作用。因为当传入一个右值时,当使用右值引用表达式引用的时候,该右值引用却是一个不折不扣的左值,那么我们要想在函数调用中继续传递右值,就需要使用std::move来进行左右值的转换。而std::move通常就是一个static_cast。不过在C++11中,用于完美转发的函数却不再叫作move,而是forward。所以我们可以将上述转发函数写成这样:

template <typename T>
void IamForwarding(T&& t) {
	IrunCodeActually(std::forward<T>(t));
}

总结引用折叠规则如下:

  1. 所有的右值引用叠加到右值引用上仍然使一个右值引用;
  2. 所有的其他引用类型之间的叠加都将变成左值引用。

完美转发用例

#include <iostream>
using namespace std;

void RunCode(int && m) { cout << "rvalue ref" << endl; }
void RunCode(int & m) { cout << "lvalue ref" << endl; }
void RunCode(const int && m) { cout << "const rvalue ref" << endl; }
void RunCode(const int & m) { cout << "const lvalue ref" << endl; }

template <typename T>
void PerfectForward(T &&t) {   RunCode(forward<T>(t));    }

int main() {
    int a;
    int b;
    const int c = 1;
    const int d = 0;

    PerfectForward(a);          // lvalue ref
    PerfectForward(move(b));    // rvalue ref
    PerfectForward(c);          // const lvalue ref
    PerfectForward(move(d));    // const rvalue ref
}

注意:只有当发生自动类型推断时(如函数模板的类型自动推导,或auto关键字),&&才是一个universal references

关键字forward

从完美转发的例子可以看出要实现完美转发需要std::forward的配合。
下面是C++11std::forward的实现源码。它一共有两个版本,一个是处理左值,一个处理右值。
在参数中通过std::remove_reference解除引用。在函数体中使用static_cast

 /**
   *  @brief  Forward an lvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

  /**
   *  @brief  Forward an rvalue.
   *  @return The parameter cast to the specified type.
   *
   *  This function is used to implement "perfect forwarding".
   */
  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
		    " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章