完美转发

所谓完美转发(perfect formarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。比如:

template <typename T>
void IamForwording(T t)
{
    IrunCodeActually(t);
}

这个简单的例子中,IamForWording是一个转发函数模板。而函数IrunCodeActually则是真正执行代码的目标函数。对于目标函数而言,总是希望转发函数将参数按照传入时的类型传递,而不产生额外的开销,就好像转发者不存在一样。

这似乎是一件非常简单的事情,但实际却不容易。在上例中,我再IamForwording的参数中使用了最基本类型进行转发,该方法会导致参数在传给IrunCodeActually之前就产生了一次额外的临时对象拷贝。因此这样的转发智能说是正确的转发,但谈不上完美。

所以通常程序员需要的是一个引用类型,引用类型不会有拷贝的开销。其次,则需要考虑转发函数对类型的接受能力。因为目标函数可能需要能够既接受左值引用,又接受右值引用,那么如果转发函数智能接受其中的一部分,我们也无法做到完美转发。我们可能会想到“万能”的常量左值类型。不过以常量左值为参数的类型函数却会遇到一些尴尬,比如:

void IrunCodeActually(int t){}
template <typename T>
void IamForwording(const T &T)
{
    IrunCodeActually(t);
}

这里,由于目标函数的参数类型是非常量左值引用类型,因此无法接受常量左值引用作为参数,这样一来,虽然转发函数的接受能力很高,但在目标函数的接受上却出了问题。如果我们的目标函数的参数是一个右值引用的,同样无法接受任何左值类型作为参数。

那C++11是如何解决完美转发的问题的呢?实际上,C++11是通过引入一条所谓“引用折叠”的新语言规则,并结合新的模板推导规则来完成完美转发。

在C++11以前,形如下列语句:

typedef const int T;
typedef T& TR;
TR &v = 1;

其中TR&v = 1这样的表达式会被编译器认为是不合法的表达式,而在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式,具体如下:

这个规则不难记忆,因为一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。而模板对类型的推导规则就比较简单,当转发函数的实参是类型X的一个左值引用,那么模板参数被推导为X&类型,而转发函数的实参是类型X的一个右值引用的话,那么模板的参数被推到为X&&类型。结合以上的引用折叠规则,就能确定出参数的实际类型。进一步,我们可以把转发函数写成如下形式:

template <typename T>
void IamForwording(T&&t)
{
    IrunCodeActually(static_cast<T && >(t);
}

注意一下,我们不仅在参数部分使用了T&&这样的标识,在目标函数传参的强制类型转换中也使用了这样的形式。比如我们调用转发函数时传入了一个X类型的左值引用,可以想象,转发函数将被实例化为如下形式:

void IamForwording( X& &&t)
{
    IrunCodeActually(static_cast<X& && >(t);
}

应用上引用折叠规则,就是:

void IamForwording( X&)
{
    IrunCodeActually(static_cast<X& >(t);
}

这样一来我们的左值传递就毫无问题了。实际使用的时候,IrunCodeActually如果接受左值引用的话,就可以直接调用转发函数。不过读者可能发现,这样调用前的static_cast没有什么作用。事实上,这里的static_cast是留给传递右值用的。

而如果我们调用转发函数时传入一个X类型的右值引用的话,我们的转发函数将被实例化为:

void IamForwording( X&& &&t)
{
    IrunCodeActually(static_cast<X&& && >(t);
}

应用上应用折叠规则,就是:

void IamForwording( X&&t)
{
    IrunCodeActually(static_cast<X&& >(t);
}

这里我们就看到了static_cast的重要性。对于一个右值而言,当它使用右值引用表达式引用的使用,该右值确实一个不折不扣的左值,那么我们想在函数调用中继续传递右值,就需要使用std::move来进行左右值的转换。而std::move通常就是一个static_cast。不过在C++11中,用于完美转发的函数却不是move,而是另外一个名字:forward。所以我们可以把转发函数写成这样:

template <typename T>
void IamForwording( T &&t)
{
    IrunCodeActually(forword(t));
}

参考:《深入理解C++11》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值