C++学习笔记总结练习:C++完美转发

本文介绍了C++中的完美转发概念,如何通过C++98和C++11的左值引用、右值引用以及std::forward实现函数模板参数的精确值类别转发,包括C++11中引入的“万能引用”和“引用折叠规则”。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

完美转发

1 概念

首先解释一下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。

template<typename T>
void function(T t) {
    otherdef(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++ 语言,我们可以采用函数模板重载的方式实现完美转发

#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
    cout << "lvalue\n";
}
void otherdef(const int & t) {
    cout << "rvalue\n";
}
//重载函数模板,分别接收左值和右值
//接收右值参数
template <typename T>
void function(const T& t) {
    otherdef(t);
}
//接收左值参数
template <typename T>
void function(T& t) {
    otherdef(t);
}
int main()
{
    function(5);//5 是右值
    int  x = 1;
    function(x);//x 是左值
    return 0;
}
// 程序执行结果为:
// rvalue
// lvalue

从输出结果中可以看到,对于右值 5 来说,它实际调用的参数类型为 const T& 的函数模板,由于 t 为 const 类型,所以 otherdef() 函数实际调用的也是参数用 const 修饰的函数,所以输出“rvalue”;对于左值 x 来说,2 个重载模板函数都适用,C++编译器会选择最适合的参数类型为 T& 的函数模板,进而 therdef() 函数实际调用的是参数类型为非 const 的函数,输出“lvalue”。

3 C++ 11实现完美转发。

  • C++ 11 标准中允许在函数模板中使用右值引用来实现完美转发

万能引用规则

  • C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)

  • 在 C++11 标准中实现完美转发,只需要编写如下一个模板函数即可

template <typename T>
void function(T&& t) {
    otherdef(t);
}

引用折叠规则

  • 此模板函数的参数 t 既可以接收左值,也可以接收右值。但仅仅使用右值引用作为函数模板的参数是远远不够的,还有一个问题继续解决,即如果调用 function() 函数时为其传递一个左值引用或者右值引用的实参。

  • C++ 11标准为了更好地实现完美转发,特意为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):

    • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
    • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。
int n = 10;
int & num = n;
function(num); // T 为 int&
int && num2 = 11;
function(num2); // T 为 int &&

Foward模板函数

  • 引入了一个模板函数 forword(),我们只需要调用该函数,将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?
#include <iostream>
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
    cout << "lvalue\n";
}
void otherdef(const int & t) {
    cout << "rvalue\n";
}
//实现完美转发的函数模板
template <typename T>
void function(T&& t) {
    otherdef(forward<T>(t));
}
int main()
{
    function(5);
    int  x = 1;
    function(x);
    return 0;
}
// 程序执行结果为:
// rvalue
// lvalue
  • 总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。

1 问题定义

  • 完美转发就是将函数实参以其原本的值类别转发出去。转发值类别
void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename /*T*/> void bar(/*T*/ x) { /*call foo with x*/ }

int main() {
  int i;
  bar(i);  // expecting output: lvalue
  bar(1);  // expecting output: rvalue
}
  • 在这里,变量 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;

typedef int&& rref;
int n;
lref&  r1 = n; // type of r1 is int& ,  int&  + &  => int &
lref&& r2 = n; // type of r2 is int& ,  int&  + && => int &
rref&  r3 = n; // type of r3 is int& ,  int&& + &  => int &
rref&& r4 = 1; // type of r4 is int&&,  int&& + && => int &&

举例说明

void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename T> int bar(T &&x);  // x is a forwarding reference

int main() {
  int i = 1;
  int &lref = i;
  int &&rref = 1;

  bar(1);                 // T 为 int,   decltype(x) 为 int&&
  bar(i);                 // T 为 int&,  decltype(x) 为 int&
  bar(lref);              // T 为 int&,  decltype(x) 为 int&
  bar(rref);              // T 为 int&,  decltype(x) 为 int&
  bar(std::move(rref));   // T 为 int,   decltype(x) 为 int&&
  bar<int &&>(1);         // T 为 int&&, decltype(x) 为 int&&
}

第一步转发问题解决

  • 解决了调用 bar 时参数值类别的问题,现在我们将 bar 的参数传递给
foo:void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename T> int bar(T &&x) { foo(x); }

3 forward转发

forward原理

  • std::forward 的实现如下:
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
  static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
                " substituting _Tp is an lvalue reference type");
  return static_cast<_Tp&&>(__t);
}
  • 实现了将左值转发为左值或右值,将右值转发为右值。

举例说明

void foo(const int &)  { std::cout << "lvalue" << std::endl; }
void foo(const int &&) { std::cout << "rvalue" << std::endl; }

int main() {
  int i = 1;
  foo(std::forward<int>(i));     // output: rvalue; forward lvalue -> rvalue
  foo(std::forward<int&>(i));    // output: lvalue; forward lvalue -> lvalue
  foo(std::forward<int&&>(i));   // output: rvalue; forward lvalue -> rvalue
  foo(std::forward<int>(1));     // output: rvalue; forward rvalue -> rvalue
  foo(std::forward<int&>(1));    // error: static_assert failed due to requirement '!is_lvalue_reference<int &>::value' "can not forward an rvalue as an lvalue"
  foo(std::forward<int&&>(1));   // output: rvalue; forward rvalue -> rvalue
}

第二步转发的实现

void foo(int &)  { std::cout << "lvalue" << std::endl; }
void foo(int &&) { std::cout << "rvalue" << std::endl; }
template<typename T> int bar(T &&x) { foo(std::forward<T>(x)); }

int main() {
  int i = 1;
  bar(i);  // output: lvalue
  bar(1);  // output: rvalue
}
  • 我们总结一下。完美转发问题是将函数的参数以其原本值类别转发出去的问题。转发引用 和 std::forward 共同解决了完美转发问题。其中,转发引用将函数的左值实参推导为左值引用类型,右值实参推导为右值引用类型。std::forward 将左值引用类型的实参转发为左值,将右值引用类型的实参转发为右值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

onnx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值