1.3值类别(value category)-1.3.3-转发引用和完美转发

c++11中引入了转发引用特征。而在没有转发引用的时代,泛式编程会面临一些问题,考虑如下代码:

template<typename T,typename Arg>
UniquePtr<T> makeUnique(const Arg& arg)
{return UniquePtr<T>(new T(arg));}

makeUnique 将参数arg传递给类T从而构造出T对象,再返回被uniquePtr包裹的智能指针。若T的构造函数只接受一个左值引用参数,那么将会导致编译错误,因为这个makeUnique只传递常左值引用,无法被非–常左值引用绑定。为了解决这个问题,我们不得不重载一个左值引用的版本。

template<typename T,typename Arg>
UniquePtr<T> makeUnique(Arg& arg)
{return UniquePtr<T>(new T(arg));}

两个版本的makeUnique的唯一区别在于函数的入参引用类型不一样,其余均一模一样,若有多个参数,每个参数都要重载两个版本,姑要重载2n个重载版本,这是毫无意义的。

c++11引用的变参模板特性解决了多参数版本的问题,将重载版本数量下降了一个数量级。而转发引用特性解决了同一个参数的不同引用类型导致的多个重载问题。回到最初的例子,在c++11后,只需实现一个版本即可。

template<typename T,typename Arg>
UniquePtr<T> makeUnique(Arg&& arg){...........}

Arg&&引用类型能绑定左值,右值表达式,最终体现在左值引用或者右值引用,因而也被称为转发引用。先前介绍的右值引用也是&&形式,区分他们的方法为:

若Arg&&中的Arg为模板参数或者auto,那么为转发引用,若为具体类型,则为右值引用。

转发引用出现在类型推导环境下,能够保留类型的cv限定符(const 于 volatile修饰符)与值类别。

这里的arg参数的值的类别是左值(注意arg定义时的类型为转发引用类型,然而arg始终是左值表达式)。若函数实现为:return UnqiuePtr<T >(new T(arg)),由于arg表达式始终为左值,若其绑定的是右值,那么就丢失了原值的属性:这将始终为左值,因此我们需要在函数传参是保持arg的左值或者右值属性,这样便产生了std:::forward即move。

template<typename T,typename Arg>
UniquePtr<T> makeUnique(Arg&& arg)
{return UniquePtr<T>(new T(std::forward<Arg>(arg)));}

而std::forward的实现完美转发较为简单,static_cast<arg&&>(arg),他将左值表达式arg强制类型转换成其传递的时候类别(引用折叠),最终结构就是保留了左值或者右值特征

最后配合可变参数模板特征,从而将2n个重载版本将为1个

template<typename T,typename... Arg>
UniquePtr<T> makeUnique(Arg&&... arg)
{return UniquePtr<T>(new T(std::forward<Arg>(arg))...);}

typename…Args声明了一个可变模板参数包Args,当…出现在标识符后边时,对参数包进行展开,因此上述代码和下述代码等价。

template<typename T,typename Arg1>
UniquePtr<T> makeUnique(Arg&&arg1)
{return UniquePtr<T>(new T(std::forward<Arg1>(arg1)));}
template<typename T,typename Arg2,typename Arg2 >
UniquePtr<T> makeUnique(Arg&& arg1,Arg&& arg2)
{return UniquePtr<T>(new T(std::forward<Arg1>(arg1),std::forward<Arg2>(arg2)));}

转发引用和完美转发固定搭配:可变参数魔板解决函数任意多个参数从而无需手写多个重载版本

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部