C++中的完美转发
来源:互联网 发布:自动化学报 软件学报 编辑:程序博客网 时间:2024/05/15 23:54
C++中的完美转发(perfect-forwarding)到底是什么?说到底,它其实就是一个类型转换,能够将传递到母函数的参数原封不动(这里的原封不动不仅指值不变,还包括类型信息,限定符之类的)在转发给其他函数。
表示形式就是这样:
func(expr); //处理函数fwd(expr){ func(std::forward(expr)); //这里省略了很多细节}
fwd就是上面我们说的母函数,我们调用fwd时传递进去的参数,如何原封不动的传递给其他函数,那就是完美转发要做的事。
下面先看一下C++中的关于它的源码实现:
//type_traits文件 // TEMPLATE FUNCTION forwardtemplate<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 forwardtemplate<typename T> T&& forward(typename remove_reference<T>::type& _Arg) { // forward an lvalue as either an lvalue or an rvalue return (static_cast<T&&>(_Arg));}template<typename T> T&& forward(typename remove_reference<T>::type&& _Arg) { // forward an rvalue as an rvalue return (static_cast<T&&>(_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<T&&>(_Arg));}
既然知道了完美转发的原理,那么我们其实可以用另外一种方式来表达完美转发,考虑下面的例子:
#include <iostream>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<T>(a));}int main(){ int a; fwd(a); //打印 lvalue fwd(std::move(a)); //打印 rvalue cout << endl << endl; return 0;}
当fwd函数传递的是一个左值,就打印lvalue,右值则打印rvalue,很好理解。那么换一种实现:
#include <iostream>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 <iostream>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<T&&)>(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 <iostream>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<T)>(a));}int main(){ int a; fwd(a); //打印 lvalue fwd(std::move(a)); //打印 lvalue cout << endl << endl; return 0;}
就只能打印lvalue了。为什么?考虑两种情况:
- 当调用fwd(a)时,传递一个左值,test模板的T类型推断成int&,所以static_cast<int&>(a)就是将a转换成一个左值引用,调用test的左值版本,没问题
- 当调用fwd(std::move(a))时,传递一个右值,test模板的T类型推断成int,static_cast<int>(a)就是将a转换成int类型,还是一个左值,所以调用test的左值版本。
另外,记住我们写代码有时候可能有多种表达方式,但是我们应该选择一种更直观的方式去表达。比如std::forward可以完全替代std::move(),比如上面的:
fwd(std::move(a));
可以写成:
fwd(std::forward<int>(a));
但是这样代码就没有那么直观了,意图不明显,比较难读懂。
所以,即使你知道可以使用类型转换实现完美转发,但是还是尽量用std::forward函数吧,因为这样意图很明显,很容易就知道你想表达什么。
- C++中的完美转发
- forward在委托机制中的应用——完美转发
- forward在委托机制中的应用——完美转发
- [C++] 右值引用:移动语义与完美转发
- C++ 11完美转发
- std:forward 完美转发
- C++ 11完美转发
- std::forward 完美转发
- std:forward 完美转发
- C++完美转发
- objective-c中的消息转发
- EL表达式中的 c 标签(完美总结)
- 右值引用、完美转发
- 【原】C++ 11完美转发
- 引用折叠和完美转发
- C++11 forward完美转发
- 模板类 的完美转发
- [C++] 右值引用:移动语义与完美转发(C++是一种扼杀生命的语言)
- 梯度反向传播求解示例
- 使用PHP在Dreamweaver下实现用户信息添加
- SDUT-1168 C语言实验——大小写转换
- 进一步理解!linux下bus,device,driver三者关系
- TCP 和 UDP
- C++中的完美转发
- 超市管理系统
- php调用sqlserver存储过程取得返回值
- android 悬浮按钮
- SDUT-1176 C语言实验——删除指定字符
- 二进制
- 拆装机
- Java Collection
- 如何在“运行”里打开软件