这又带来了新的问题,假如F的参数是一个非常量左值引用,则调用G后,我们可以通过F来修改传入的常量左值和常量右值了,而这是非常危险的。
?
?
?
转发方案五:非常量左值引用 + 修改的参数推导规则。
这种方案与方案一类似,但需要修改现有的参数推导规则,即传递一个非常量右值给模板类型时,将它推导成常量右值,这样就解决了方案一中无法接收非常量右值的参数的问题。但由于修改了现有的参数推导规则,因此会导致已有代码的语义发生改变。考虑下面的代码。
?
?
?
?
1 template
2 void F(A &a)
3 {
4 cout << void F(A& a) << endl;
5 }
6
7 void F(const long &a)
8 {
9 cout << void F(const long &a) << endl;
10 }
?
?
?
在未修改参数推导规则前,调用F(10)会选择第二个重载函数,但修改后,却会调用第一个重载函数,这就给C++带来了兼容性的问题。
?
?
?
转发方案六:右值引用。考虑下面的代码。
?
?
1 template
2 void G(A &&a)
3 {
4 F(a);
5 }
?
在这种方案中,G将无法接收左值,因为不能将一个左值传递给一个右值引用。另外,当传递非常量右值时也会存在问题,因为此时a本身是一个左值,这样当F的参数是一个非常量左值引用时,我们就可以来修改传入的非常量右值了。
?
?
?
转发方案七:右值引用 + 修改的参数推导规则。
要理解修改后的参数推导规则,我们先来看一下引用叠加规则:
1、T& + & = T&
2、T& + && = T&
3、T&& + & = T&
4、T或T&& + && = T&&
修改后的针对右值引用的参数推导规则为:若函数模板的模板参数为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 template
2 void G(A &&a)
3 {
4 F(static_cast(a));
5 }
?
当传给G一个左值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为左值引用类型T&,根据推导规则1,模板参数A被推导为T&。这样,在G内部调用F(static_cast(a))时,static_cast(a)等同于static_cast
(a),根据引用叠加规则第2点,即为static_cast
(a),这样转发给F的还是一个左值。
当传给G一个右值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为右值引用类型T&&,根据推导规则2,模板参数A被推导为T。这样,在G内部调用F(static_cast(a))时,static_cast(a)等同于static_cast
(a),这样转发给F的还是一个右值(不具名右值引用是右值)。
可见,使用该方案后,左值和右值都能正确地进行转发,并且不会带来其他问题。另外,C++ 11为了方便转发的实现,提供了一个函数模板forward,用于参数的完美转发。使用forward后的代码可简化为:
?
?
1 template
2 void G(A &&a)
3 {
4 F(forward(a));
5 }
?
为了便于进行各种转发方案的比较,下面以表格的形式列出了各自的特性。
?
转发方案非常量左值常量左值非常量右值常量右值修改语言已知问题
?
一、非常量左值引用非常量左值常量左值无法转发常量左值否无法接收非常量右值的参数
二、常量左值引用常量左值常量左值常量左值常量左值否无法将常量左值引用转发给非常量左值引用
三、非常量左值引用 + 常量左值引用非常量左值常量左值常量左值常量左值否重载函数过多,实际编码不可行
四、常量左值引用 + const_cast非常量左值非常量左值非常量左值非常量左值否可修改常量左值和常量右值,不安全
五、非常量左值引用 + 修改的参数推导规则非常量左值常量左值常量左值常量左值是会导致兼容性问题,且不支持移动语义
六、右值引用无法转发无法转发非常量左值常量左值是可修改非常量右值,不安全
七、右值引用 + 修改的参数推导规则非常量左值常量左值非常量右值常量右值是暂无,故简称为完美转发
?
?
?