[006]Reference in C++C++11及其完美转发

其实同事已经对C++0X中的完美转发有了很好的理解,在这里我只是在自己的基础上衍生的一些理解加上去

同事博客C++完美转发地址参考:http://www.cnblogs.com/alephsoul-alephsoul/archive/2013/01/10/2853900.html

引言

  其实完美转发,其实就是在调用了函数的基础上,能够相当于调用第二个函数,那么这个函数中的参数就完美转发给了第二个函数。

转发方式一:非常量左值引用

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a1, int a2, int a3) {
 5     cout << a1 + a2 + a3 <<endl;
 6 }
 7  
 8 template<class A1, class A2, class A3>
 9 void G(A1& a1, A2& a2, A3& a3) {
10    return F(a1, a2, a3);
11 }
12 
13 int main() {
14     int i,j,k;
15     G(i, j, k);
16     G(1, 2, 3);
17     F(1, 2, 3);
18 
19     system("pause");
20 }

16行代码中G(1,2,3)是不能编译通过的,因为我们无法接收非常量右值的参数。

但是,代码中15行是完全可以编译通过的,虽然此时的i,j,k均是左值,但此时的G(i, j, k)实际上就是取了i,j,k的任意值相加。

F(1,2,3)可以完全略去不讲。

转发方式二:常量左值引用

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int& a1, int& a2, int& a3) {
 5     cout << a1 + a2 + a3 <<endl;
 6 }
 7  
 8 template<class A1, class A2, class A3>
 9 void G(A1 const & a1, const A2& a2,const A3& a3) {
10    return F(a1, a2, a3);
11 }
12 
13 int main() {
14     int i,j,k;
15     G(i, j, k);
16     G(1, 2, 3);
17 
18     system("pause");
19 }

此时的15、16行均会出错,原因就在于无法将一个常量左值引用转发给一个非常量左值引用。

虽然15行中传入的是非常量左值,但是G函数可供接收的却是常量左值引用,因此失败。

转发方式三:非常量左值引用+常量左值引用

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a) {
 5     cout << a <<endl;
 6 }
 7  
 8 template<class A>
 9 void G(A &a) {
10    return F(a);
11 }
12 
13 template<class A>
14 void G(const A &a) {
15    return F(a);
16 }
17 
18 int main() {
19     int i;
20     G(i);
21     G(1);
22 
23     system("pause");
24 }

通过函数重载来让转发时找到合适的转发方式,这种方式倒是实现了转发,但是却非常的不“完美”,详细请看同事的博客。

需要注意的是,第20行中,G(i)由于i是非常量左值,因此进入的是G(A &a)函数,但是由于i未被初始化,所以出现的是一个任意值。

转发方式四:常量左值引用+const_cast

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a) {
 5     cout << a <<endl;
 6 }
 7  
 8 template<class A>
 9 void G(const A &a) {
10    return F(const_cast<A &>(a));
11 }
12 
13 int main() {
14     int i;
15     G(i);
16     G(1);
17 
18     system("pause");
19 }

整个过程编译是没有问题的,但是通过const_cast,却将传入的const属性去除了,这样在调用G()函数后,我们就可以通过F()函数来修正传入的常量左值和常量右值了。当一个参数的属性都被修正了,还叫“完美”么?

转发方式五:非常量左值引用+修改的参数推倒规则转发

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a) {
 5     cout << a <<endl;
 6 }
 7  
 8 template<class A>
 9 void G(A &a) {
10    return F(a);
11 }
12 
13 void G(const long &a) {
14     return F(a);
15 }
16 
17 int main() {
18     int i;
19     G(i);
20     G(1);
21 
22     system("pause");
23 }

奈何啊,模板编程的参数推倒规则不懂啊,这里理解起来非常困难。

按照我的理解,其实修正后的模板规则跟函数重载差不多,系统会自动匹配最佳的规则。

转发方式六:右值引用

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a) {
 5     cout << a <<endl;
 6 }
 7  
 8 template<class A>
 9 void G(A &&a) {
10    return F(a);
11 }
12 
13 int main() {
14     int i;
15     G(i);
16     G(1);
17 
18     system("pause");
19 }

第9行的代码就是C++11中提出的右值引用新标准,如果你在VS08中编译的话是不会通过的,只有在10以上的才可以。

同事的博客上写的是不能将一个左值传递给一个右值引用,这句话本身是没有问题的,但是在代码15行中,i是作为一个左值的出现,所以需要注意的是传到G(A &&a)中的参数其实是一个为2的右值,在G()函数中并没有保证传递参数的属性,所以也算不上完美转发。

 转发方式七:右值引用+修改的参数推倒规则转发

1、T& + & = T&
2、T& + && = T&
3、T&& + & = T&
4、T或T&& + && = T&&

上述的4个引用叠加规则的理解至关重要,修改后的针对右值引用的参数推导规则为:若函数模板的模板参数为A,模板函数的形参为A&&,则可分为两种情况讨论:

1、若实参为T&,则模板参数A应被推导为引用类型T&。(由引用叠加规则第2点T& + && = T&和A&&=T&,可得出A=T&)

2、若实参为T&&,则模板参数A应被推导为非引用类型T。(由引用叠加规则第4点T或T&& + && = T&&和A&&=T&&,可得出A=T或T&&,强制规定A=T)

基于以上的分析,我们可以写出以下的代码:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a) {
 5     cout << a <<endl;
 6 }
 7  
 8 template<class A>
 9 void G(A &&a) {
10    return F(static_cast<A &&>(a));
11 }
12 
13 int main() {
14     int i = 2;
15     G(i);
16     G(1);
17 
18     system("pause");
19 }

   当传给f一个左值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为左值引用类型T&,根据推导规则1,模板参数A被推导为T&。这样,在f内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T& &&>(a),根据引用叠加规则第2点,即为static_cast<T&>(a),这样转发给g的还是一个左值。

   当传给f一个右值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为右值引用类型T&&,根据推导规则2,模板参数A被推导为T。这样,在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T&&>(a),这样转发给F的还是一个右值(不具名右值引用是右值)。

   这样看来,无论左值还是右值,都实现了完美的转发,所以这种被称为是完美转发。

   在C++11中,专门提供了一个函数模板来实现完美转发,他就是forward。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void F(int a) {
 5     cout << a <<endl;
 6 }
 7  
 8 template<class A>
 9 void G(A &&a) {
10    return F(forward<A &&>(a));
11 }
12 
13 int main() {
14     int i = 2;
15     G(i);
16     G(1);
17 
18     system("pause");
19 }

上述代码需要在VS10基础上运行,至此,完美转发均完全实现了。

                                                                                                                                                                     

原文地址:https://www.cnblogs.com/hustcser/p/2946271.html