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.

顺序 选择 循环 总结

posted @ 2020-01-27 11:46  kaizenly  阅读(7120)  评论(0编辑  收藏  举报
打赏