C++中的完美转发

来源:互联网 发布:自动化学报 软件学报 编辑:程序博客网 时间:2024/05/15 23:54

C++中的完美转发(perfect-forwarding)到底是什么?说到底,它其实就是一个类型转换,能够将传递到母函数的参数原封不动(这里的原封不动不仅指值不变,还包括类型信息,限定符之类的)在转发给其他函数。
表示形式就是这样:

func(expr);  //处理函数fwd(expr){    func(std::forward(expr)); //这里省略了很多细节}

fwd就是上面我们说的母函数,我们调用fwd时传递进去的参数,如何原封不动的传递给其他函数,那就是完美转发要做的事。

下面先看一下C++中的关于它的源码实现:

//type_traits文件    // TEMPLATE FUNCTION forwardtemplate<class _Ty> inline    constexpr _Ty&& forward(        typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT    {   // forward an lvalue as either an lvalue or an rvalue    return (static_cast<_Ty&&>(_Arg));    }template<class _Ty> inline    constexpr _Ty&& forward(        typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT    {   // forward an rvalue as an rvalue    static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");    return (static_cast<_Ty&&>(_Arg));    }

这里我简化一下,让你们看的更加明白:

    // TEMPLATE FUNCTION forwardtemplate<typename T>  T&& forward(typename remove_reference<T>::type& _Arg) {       // forward an lvalue as either an lvalue or an rvalue    return (static_cast<T&&>(_Arg));}template<typename T> T&& forward(typename remove_reference<T>::type&& _Arg) {       // forward an rvalue as an rvalue    return (static_cast<T&&>(_Arg));}

去掉了很多优化(如constexpr,noexcept等)和一些静态断言,另外typename可以完全替代class,然后下面开始分析:
首先,可以看出来,forward函数对于参数是左值和右值都实现了一个版本,上面的接收左值,下面的接收右值参数。remove_reference::type就是将T的引用全部去除,比如T是int&,就变成了int。然后后面加上一个&就是左值引用,加上&&就是右值引用。
然后就是执行以下类型转换。这里的T&&其实是一个universal引用,关于universal引用,可以参考这篇文章,这里就不再赘述。
理解了universal引用,上面的源代码就极其容易理解了。其实,上面的两个版本我觉得可以用一个就实现了:

template<typename T>  T&& forward(T&& _Arg) {       return (static_cast<T&&>(_Arg));}

既然知道了完美转发的原理,那么我们其实可以用另外一种方式来表达完美转发,考虑下面的例子:

#include <iostream>void test(int& a){    std::cout<<"lvalue"<<std::endl;}void test(int&& a){    std::cout<<"rvalue"<<std::endl;}template<typename T>void fwd(T&& a){    test(std::forward<T>(a));}int main(){       int a;    fwd(a);            //打印 lvalue    fwd(std::move(a)); //打印 rvalue    cout << endl << endl;    return 0;}

当fwd函数传递的是一个左值,就打印lvalue,右值则打印rvalue,很好理解。那么换一种实现:

#include <iostream>void test(int& a){    std::cout<<"lvalue"<<std::endl;}void test(int&& a){    std::cout<<"rvalue"<<std::endl;}template<typename T>void fwd(T&& a){    test(static_cast<decltype(a)>(a));}int main(){       int a;    fwd(a);            //打印 lvalue    fwd(std::move(a)); //打印 rvalue    cout << endl << endl;    return 0;}

运行结果和上面一模一样。因为decltype(a)会得出a的真正类型,所以就将它转换成它真正的类型。
上面就等价于:

#include <iostream>void test(int& a){    std::cout<<"lvalue"<<std::endl;}void test(int&& a){    std::cout<<"rvalue"<<std::endl;}template<typename T>void fwd(T&& a){    test(static_cast<T&&)>(a));}int main(){       int a;    fwd(a);            //打印 lvalue    fwd(std::move(a)); //打印 rvalue    cout << endl << endl;    return 0;}

a的类型就是T&&(这里是一个universal引用类型),所以decltype(a)就是T&&。
但是如果修改成下面这样:

#include <iostream>void test(int& a){    std::cout<<"lvalue"<<std::endl;}void test(int&& a){    std::cout<<"rvalue"<<std::endl;}template<typename T>void fwd(T&& a){    test(static_cast<T)>(a));}int main(){       int a;    fwd(a);            //打印 lvalue    fwd(std::move(a)); //打印 lvalue    cout << endl << endl;    return 0;}

就只能打印lvalue了。为什么?考虑两种情况:

  • 当调用fwd(a)时,传递一个左值,test模板的T类型推断成int&,所以static_cast<int&>(a)就是将a转换成一个左值引用,调用test的左值版本,没问题
  • 当调用fwd(std::move(a))时,传递一个右值,test模板的T类型推断成int,static_cast<int>(a)就是将a转换成int类型,还是一个左值,所以调用test的左值版本。

另外,记住我们写代码有时候可能有多种表达方式,但是我们应该选择一种更直观的方式去表达。比如std::forward可以完全替代std::move(),比如上面的:

fwd(std::move(a));  

可以写成:

fwd(std::forward<int>(a));

但是这样代码就没有那么直观了,意图不明显,比较难读懂。
所以,即使你知道可以使用类型转换实现完美转发,但是还是尽量用std::forward函数吧,因为这样意图很明显,很容易就知道你想表达什么。

原创粉丝点击