C++11 完美转发
【1】为什么引入完美转发?
在函数模板编程中,常有一种场景是把模板参数转发给另一个调用函数,这时候如果只提供值传递版本会显得效率太低。看以下代码:
1 template<class TYPE, class ARG> 2 TYPE* getInstance(ARG arg) 3 { 4 TYPE* pRet = nullptr; 5 pRet = new TYPE(arg); 6 return pRet; 7 }
代码很简单,就是用ARG参数去初始化一个TYPE类型的对象,然后返回该对象指针。
考虑一下,如果ARG类型是一个自定义类型,那么这样的值传递会是比较大的性能开销。
有没有办法改进一下?再看以下代码:
1 template<class TYPE, class ARG> 2 TYPE* getInstance(const ARG& arg) 3 { 4 TYPE* pRet = nullptr; 5 pRet = new TYPE(arg); 6 return pRet; 7 }
这段代码将传入参数类型改为了“万能”的常量左值引用,可以接受任何类型,可以解决性能开销的问题。
但是,但是,还不够灵活,假如我们想TYPE接受一个右值去初始化呢?
那么有没有可以把参数连同类型一起转发的方案呢?当然有,没有C++办不到的。
C++11就提供了这样能力既完美转发。代码如下:
1 template<class TYPE, class ARG> 2 TYPE* getInstance(ARG&& arg) 3 { 4 TYPE* pRet = nullptr; 5 pRet = new TYPE(std::forward<ARG>(arg)); 6 return pRet; 7 }
嗯哼?难道把形参类型改为右值引用就可以了?懵圈....表示颠覆认知!看不懂无所谓,再往下看。
【2】引用折叠 与 模板推导
C++11是通过引入一条所谓“引用折叠”(reference collapsing)的新语言规则,并结合新的模板推导规则来实现的完美转发。
先了解一下引用折叠。如下语句:
1 typedef const int T; 2 typedef T& TR; 3 TR& v = 10; // 该声明在C++98中会导致编译错误
在C++11之前,其中TR& v = 10;这样的表达式会被编译器认为是不合法的表达式。
而在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式,
具体规则,如下图所示:
个人觉得,这个规则还是得深刻理解,记住一个原则:一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。
如下验证程序:
1 #include <iostream> 2 using namespace std; 3 4 typedef const int T; 5 typedef T& TR; 6 typedef T&& TRR; 7 8 void JudgeType() 9 { 10 cout << "lvalue_ref_type?: " << is_lvalue_reference<TR>::value << endl; // 1 11 cout << "rvalue_ref_type?: " << is_rvalue_reference<TR>::value << endl; // 0 12 13 cout << "lvalue_ref_type?: " << is_lvalue_reference<TR&>::value << endl; // 1 14 cout << "rvalue_ref_type?: " << is_rvalue_reference<TR&>::value << endl; // 0 15 16 cout << "lvalue_ref_type?: " << is_lvalue_reference<TR&&>::value << endl; // 1 17 cout << "rvalue_ref_type?: " << is_rvalue_reference<TR&&>::value << endl; // 0 18 19 cout << "lvalue_ref_type?: " << is_lvalue_reference<TRR>::value << endl; // 0 20 cout << "rvalue_ref_type?: " << is_rvalue_reference<TRR>::value << endl; // 1 21 22 cout << "lvalue_ref_type?: " << is_lvalue_reference<TRR&>::value << endl; // 1 23 cout << "rvalue_ref_type?: " << is_rvalue_reference<TRR&>::value << endl; // 0 24 25 cout << "lvalue_ref_type?: " << is_lvalue_reference<TRR&&>::value << endl; // 0 26 cout << "rvalue_ref_type?: " << is_rvalue_reference<TRR&&>::value << endl; // 1 27 } 28 29 int main() 30 { 31 JudgeType(); 32 system("pause"); 33 }
模板推导。为了便于说明问题,下面我们把函数模板写为如下形式:
1 template <typename T> 2 void IamForwording(T&& t) 3 { 4 IrunCodeActually(static_cast<T&&>(t)); 5 }
说明:IamForwording为转发函数;IrunCodeActually为目标函数(即真正执行函数过程的目标)
模板对类型的推导规则就比较简单:
当转发函数的实参是类型X的一个左值引用,那么模板参数被推导为X&类型;
当转发函数的实参是类型X的一个右值引用,那么模板的参数被推导为X&&类型。
再结合以上的引用折叠规则,就能确定出参数的实际类型。
尤其注意,我们不仅在参数部分使用了T&&这样的标识,在目标函数传参的强制类型转换中也使用了这样的形式。
这个标识不是右值引用,它有专用的名字为转发引用(forwarding reference)。
比如,我们调用转发函数时传入了一个X类型的左值引用,可以想象,转发函数将被实例化为如下形式:
1 void IamForwording(X& && t) 2 { 3 IrunCodeActually(static_cast<X& &&>(t)); 4 }
应用上引用折叠规则,就是:
1 void IamForwording(X& t) 2 { 3 IrunCodeActually(static_cast<X&>(t)); 4 }
如此一来,左值传递就毫无问题了。
实际使用的时候,IrunCodeActually如果接受左值引用的话,就可以直接调用转发函数。
不过你可能会发现,这里调用前的static_cast没有什么作用。
事实上,这里的static_cast是留给传递右值用的。如下分析。
如果我们调用转发函数时传入了一个X类型的右值引用的话,我们的转发函数将被实例化为:
1 void IamForwording(X&& && t) 2 { 3 IrunCodeActually(static_cast<X&& &&>(t)); 4 }
应用上引用折叠规则,就是:
1 void IamForwording(X&& t) 2 { 3 IrunCodeActually(static_cast<X&&>(t)); 4 }
这里我们就看到了static_cast的重要性。
事实上,对于一个右值而言,当它使用右值引用表达式引用的时候,该右值引用却是个不折不扣的左值。
那么我们想在函数调用中继续传递右值,就需要使用std::move来进行左值向右值的转换。
而std::move通常就是一个static_cast。不过在C++11中,用于完美转发的函数却不再叫作move,而是另外一个名字:forward。
move和forward在实际实现上差别并不大。
不过标准库这么设计,也许是为了让每个名字对应于不同的用途,以应对未来可能的扩展。
推荐在实现完美转发时使用forward。
【3】完美转发的应用
完美转发的应用示例:
1 #include <iostream> 2 using namespace std; 3 4 void fun(int& x) { cout << "call lvalue ref" << endl; } 5 void fun(int&& x) { cout << "call rvalue ref" << endl; } 6 void fun(const int& x) { cout << "call const lvalue ref" << endl; } 7 void fun(const int&& x) { cout << "call const rvalue ref" << endl; } 8 9 template<typename T> 10 void PerfectForward(T&& t) 11 { 12 std::cout << "T is a ref type?: " << std::is_reference<T>::value << std::endl; 13 std::cout << "T is a lvalue ref type?: " << std::is_lvalue_reference<T>::value << std::endl; 14 std::cout << "T is a rvalue ref type?: " << std::is_rvalue_reference<T>::value << std::endl; 15 16 fun(forward<T>(t)); 17 } 18 19 int main() 20 { 21 PerfectForward(10); // call rvalue ref 22 23 int a = 5; 24 PerfectForward(a); // call lvalue ref 25 PerfectForward(move(a)); // call rvalue ref 26 27 const int b = 8; 28 PerfectForward(b); // call const lvalue ref 29 PerfectForward(move(b)); // call const rvalue ref 30 31 system("pause"); 32 return 0; 33 } 34 35 /* 36 T is a ref type?: 0 37 T is a lvalue ref type?: 0 38 T is a rvalue ref type?: 0 39 call rvalue ref 40 T is a ref type?: 1 41 T is a lvalue ref type?: 1 42 T is a rvalue ref type?: 0 43 call lvalue ref 44 T is a ref type?: 0 45 T is a lvalue ref type?: 0 46 T is a rvalue ref type?: 0 47 call rvalue ref 48 T is a ref type?: 1 49 T is a lvalue ref type?: 1 50 T is a rvalue ref type?: 0 51 call const lvalue ref 52 T is a ref type?: 0 53 T is a lvalue ref type?: 0 54 T is a rvalue ref type?: 0 55 call const rvalue ref 56 */
所有4种类型的值对完美转发进行测试,可以看到,所有的转发都被正确地送到了目的地。
注意分析,加深理解。
good good study, day day up.
顺序 选择 循环 总结