3 C++完美转发
完美转发
1 概念
首先解释一下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
1 | template<typename T> |
function() 函数模板中调用了 otherdef() 函数。在此基础上,完美转发指的是:如果 function() 函数接收到的参数 t 为左值,那么该函数传递给 otherdef() 的参数 t 也是左值;反之如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须为右值。
2 C++98实现完美转发
C++98通过左值引用和常量左值引用+函数重载实现完美转发
C++98/03 标准下的 C++ 也可以实现完美转发,只是实现方式比较笨拙。通过前面的学习我们知道,C++ 98/03 标准中只有左值引用,并且可以细分为非 const 引用和 const 引用。其中,使用非 const 引用作为函数模板参数时,只能接收左值,无法接收右值;而 const 左值引用既可以接收左值,也可以接收右值,但考虑到其 const 属性,除非被调用函数的参数也是 const 属性,否则将无法直接传递。
如果使用 C++ 98/03 标准下的 C++ 语言,我们可以采用函数模板重载的方式实现完美转发
1 |
|
从输出结果中可以看到,对于右值 5 来说,它实际调用的参数类型为 const T& 的函数模板,由于 t 为 const 类型,所以 otherdef() 函数实际调用的也是参数用 const 修饰的函数,所以输出“rvalue”;对于左值 x 来说,2 个重载模板函数都适用,C++编译器会选择最适合的参数类型为 T& 的函数模板,进而 therdef() 函数实际调用的是参数类型为非 const 的函数,输出“lvalue”。
3 C++ 11实现完美转发。
- C++ 11 标准中允许在函数模板中使用右值引用来实现完美转发
万能引用规则
C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)
在 C++11 标准中实现完美转发,只需要编写如下一个模板函数即可
1 | template <typename T> |
引用折叠规则
此模板函数的参数 t 既可以接收左值,也可以接收右值。但仅仅使用右值引用作为函数模板的参数是远远不够的,还有一个问题继续解决,即如果调用 function() 函数时为其传递一个左值引用或者右值引用的实参。
C++ 11标准为了更好地实现完美转发,特意为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):
- 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
- 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。
1 | int n = 10; |
Foward模板函数
- 引入了一个模板函数 forword
(),我们只需要调用该函数,将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?
1 |
|
- 总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。
1 问题定义
- 完美转发就是将函数实参以其原本的值类别转发出去。转发值类别
1 | void foo(int &) { std::cout << "lvalue" << std::endl; } |
- 在这里,变量 i 经历了两次转发,所以我们需要先后解决这两次转发的值类别问题。
- 用户调用 bar 时,参数的值类别问题当用户以左值表达式调用 bar 时,确保其实例化(Instantiation)的形参类型为左值引用当
- 用户以右值表达式调用 bar 时,确保其实例化的形参类型为右值引用。
- bar 调用 foo 时,参数的值类别问题
- 当 bar 的形参类型为左值引用时,将其以左值转发给 foo
- 当 bar 的形参类型为右值引用时,将其以右值转发给 foo
C++ 通过转发引用来解决第一个匹配,通过 std::forward 来解决第二个匹配。
2 转发引用
原理
- 转发引用基于一个叫做引用坍缩(Reference Collapsing)的原理:
rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference.typedef int& lref;
1 | typedef int&& rref; |
举例说明
1 | void foo(int &) { std::cout << "lvalue" << std::endl; } |
第一步转发问题解决
- 解决了调用 bar 时参数值类别的问题,现在我们将 bar 的参数传递给
1 | foo:void foo(int &) { std::cout << "lvalue" << std::endl; } |
3 forward转发
forward原理
- std::forward 的实现如下:
1 | template<typename _Tp> |
- 实现了将左值转发为左值或右值,将右值转发为右值。
举例说明
1 | void foo(const int &) { std::cout << "lvalue" << std::endl; } |
第二步转发的实现
1 | void foo(int &) { std::cout << "lvalue" << std::endl; } |
- 我们总结一下。完美转发问题是将函数的参数以其原本值类别转发出去的问题。转发引用 和 std::forward 共同解决了完美转发问题。其中,转发引用将函数的左值实参推导为左值引用类型,右值实参推导为右值引用类型。std::forward 将左值引用类型的实参转发为左值,将右值引用类型的实参转发为右值。









