C++中的完美转发(perfect-forwarding)到底是什么?说到底,它其实就是一个类型转换,能够将传递到母函数的参数原封不动(这里的原封不动不仅指值不变,还包括类型信息,限定符之类的)在转发给其他函数。
表示形式就是这样:
func(expr); //处理函数
fwd(expr)
{
func(std::forward(expr)); //这里省略了很多细节
}
fwd就是上面我们说的母函数,我们调用fwd时传递进去的参数,如何原封不动的传递给其他函数,那就是完美转发要做的事。
下面先看一下C++中的关于它的源码实现:
//type_traits文件
// TEMPLATE FUNCTION forward
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{ // forward an lvalue as either an lvalue or an rvalue
return (static_cast<_Ty&&>(_Arg));
}
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
这里我简化一下,让你们看的更加明白:
// TEMPLATE FUNCTION forward
template<typename T>
T&& forward(typename remove_reference::type& _Arg)
{
// forward an lvalue as either an lvalue or an rvalue
return (static_cast(_Arg));
}
template<typename T>
T&& forward(typename remove_reference::type&& _Arg)
{
// forward an rvalue as an rvalue
return (static_cast(_Arg));
}
去掉了很多优化(如constexpr,noexcept等)和一些静态断言,另外typename可以完全替代class,然后下面开始分析:
首先,可以看出来,forward函数对于参数是左值和右值都实现了一个版本,上面的接收左值,下面的接收右值参数。remove_reference::type就是将T的引用全部去除,比如T是int&,就变成了int。然后后面加上一个&就是左值引用,加上&&就是右值引用。
然后就是执行以下类型转换。这里的T&&其实是一个universal引用,关于universal引用,可以参考这篇文章,这里就不再赘述。
理解了universal引用,上面的源代码就极其容易理解了。其实,上面的两个版本我觉得可以用一个就实现了:
template<typename T>
T&& forward(T&& _Arg)
{
return (static_cast(_Arg));
}
既然知道了完美转发的原理,那么我们其实可以用另外一种方式来表达完美转发,考虑下面的例子:
#include
void test(int& a){
std::cout<<"lvalue"<<std::endl;
}
void test(int&& a){
std::cout<<"rvalue"<<std::endl;
}
template<typename T>
void fwd(T&& a){
test(std::forward(a));
}
int main()
{
int a;
fwd(a); //打印 lvalue
fwd(std::move(a)); //打印 rvalue
cout << endl << endl;
return 0;
}
当fwd函数传递的是一个左值,就打印lvalue,右值则打印rvalue,很好理解。那么换一种实现:
#include
void test(int& a){
std::cout<<"lvalue"<<std::endl;
}
void test(int&& a){
std::cout<<"rvalue"<<std::endl;
}
template<typename T>
void fwd(T&& a){
test(static_cast<decltype(a)>(a));
}
int main()
{
int a;
fwd(a); //打印 lvalue
fwd(std::move(a)); //打印 rvalue
cout << endl << endl;
return 0;
}
运行结果和上面一模一样。因为decltype(a)会得出a的真正类型,所以就将它转换成它真正的类型。
上面就等价于:
#include
void test(int& a){
std::cout<<"lvalue"<<std::endl;
}
void test(int&& a){
std::cout<<"rvalue"<<std::endl;
}
template<typename T>
void fwd(T&& a){
test(static_cast(a));
}
int main()
{
int a;
fwd(a); //打印 lvalue
fwd(std::move(a)); //打印 rvalue
cout << endl << endl;
return 0;
}
a的类型就是T&&(这里是一个universal引用类型),所以decltype(a)就是T&&。
但是如果修改成下面这样:
#include
void test(int& a){
std::cout<<"lvalue"<<std::endl;
}
void test(int&& a){
std::cout<<"rvalue"<<std::endl;
}
template<typename T>
void fwd(T&& a){
test(static_cast(a));
}
int main()
{
int a;
fwd(a); //打印 lvalue
fwd(std::move(a)); //打印 lvalue
cout << endl << endl;
return 0;
}
就只能打印lvalue了。为什么?考虑两种情况:
另外,记住我们写代码有时候可能有多种表达方式,但是我们应该选择一种更直观的方式去表达。比如std::forward可以完全替代std::move(),比如上面的:
fwd(std::move(a));
可以写成:
fwd(std::forward (a));
但是这样代码就没有那么直观了,意图不明显,比较难读懂。
所以,即使你知道可以使用类型转换实现完美转发,但是还是尽量用std::forward函数吧,因为这样意图很明显,很容易就知道你想表达什么。