其实同事已经对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基础上运行,至此,完美转发均完全实现了。