【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.
顺序 选择 循环 总结
原文链接: https://www.cnblogs.com/Braveliu/p/12235618.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/192423
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!