简介
该篇博客主要介绍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));
}
总结引用折叠规则如下:
- 所有的右值引用叠加到右值引用上仍然使一个右值引用;
- 所有的其他引用类型之间的叠加都将变成左值引用。
完美转发用例
#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++11
中std::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);
}