Cpp / 通用引用、引用折叠与完美转发问题

本文详细介绍了C++中的通用引用(如T&&)、引用折叠规则以及std::move的工作原理。通用引用允许绑定左值和右值,而引用折叠则规定了不同引用类型的折叠方式。std::move通过模板实现类型转换,用于将左值转换为右值引用。此外,文章还探讨了完美转发的概念,解释了如何使用通用引用和std::forward实现参数的原样转发,确保类型和属性不变。

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

一、通用引用

通用引用,允许其绑定右值(就像右值引用那样)和左值(就像左值引用那样)。而且,它们可以绑定 const 或者非 const 对象,可以绑定 volatile 和非 volatile 对象,还可以绑定 const 和 volatile 同时作用的对象。它们实际上可以绑定任何东西。构成通用引用有两个条件:

  • 必须精确满足 T&& 这种形式(即使加上 const 也不行)

  • 类型T必须是通过推断得到的(最常见的就是模板函数参数)

二、引用折叠

引用折叠规则:

  • X& &,X& &&,X&& & 折叠为:X&
  • X&& && 折叠为:X&&

三、std::move 的工作原理

由以上知识,就可以解释一下 std::move 的工作原理了:

template <typename T>
typename remove_reference<T>::type &&move(T &&t)
{
    return static_cast<typename remove_reference<T>::type &&>(t);
}

首先,move 函数的参数类型是通用引用 T&&,可以绑定任意类型参数,其次,返回值是 remove_reference<T> 的 type 成员类型的右值引用,比如 T 被推导为 int 或者 int&,则remove_reference<int&>::type 为 int 类型,返回值类型为 int&&,最后,函数体中 static_cast 内的转换过程类似,虽然不能隐式的将一个左值转换为一个右值引用,但是通过 static_cast 显式转换时允许的(把左值截断问题缩小在使用std::move代码的范围内)。

四、完美转发

有时候,某些函数需要将其实参连同类型(const、左值、右值等属性)不变的转发给其他函数。

template <typename F, typename T>
void sender(F receiver, T t) //sender函数,接受一个可调用对象和一个模板参数类型的参数
{
    receiver(t); //sender需要将自己的参数t转发给receiver函数
}

一般情况下这个函数能工作,但是当它调用一个接受引用类型参数的函数时就会有问题:

void rec(int &i) { ++i; }
int j = 1;
rec(j);
cout << j << endl; //输出 j 为 2;

//但是通过 sender 调用时:
template <typename F, typename T>
void sender(F receiver, T t)
{
    receiver(t);
}
sender(rec, j);
cout << j << endl; //输出 j 为 1 。

其原因是 j 传递给 sender 函数,推断出 T 为 int 类型而非引用,j 的值是被拷贝到形参 t 中的,因此对 t 值的改变不会反应到 j 中。

这时联想到我们讲的通用引用,将模板参数类型定义为 T&&,接受左值时,T 会被推断为左值引用类型,经过一次引用折叠,得参数 t 的类型为左值引用,它对应实参的 const 属性和左值、右值属性都将得到保持:

template <typename F, typename T>
void sender(F receiver, T &&t) //通用引用
{
    receiver(t);
}

void rec(int &i)
{
    ++i;
}
sender(rec, j);
cout << j << endl; //OK!输出 j 为 2 。

但是这儿又会遇到另一个问题:

template <typename F, typename T>
void sender(F receiver, T &&t)
{
    receiver(t);
}

void rec(int &&i) //现在rec接受一个右值引用参数
{
    ++i;
}

int j = 1;
sender(rec, j); //错误:无法从一个左值实例化int&&
sender(rec, 1); //错误:无法从一个左值实例化int&&

当我们试图对一个接受右值引用的函数转发参数时,会报以上错误,不论我们传递给 sender 函数的是一个左值还是右值。原因是传递给 rec 函数中形参 i 的是 sender 函数中的参数 t,函数参数和其他变量一样都是左值表达式!所以会出现将左值绑定到右值引用的错误。

这时需要用到 forward 函数来保证:当 sender 函数接受一个右值实参,转发给 rec 函数时仍然能保持其右值属性。forward 函数定义在 <utility> 头文件中。

//forward函数
//lvalue (1)
template <typename Type>
Type &&forward(typename remove_reference<Type>::type &arg) noexcept
{
    return (static_cast<Type &&>(arg));
}
//rvalue (2)
template <typename Type>
Type &&forward(typename remove_reference<Type>::type &&arg) noexcept
{
    return (static_cast<Type &&>(arg));
}

forward 函数的工作原理:由 arg 接受的实参类型推断出 Type 类型。
forward 函数必须通过显式模板实参来调用,它跟通用引用配合可以保存原始实参的所有特性,回到我们的例子:

template <typename F, typename T>
void sender(F receiver, T &&t)
{
    receiver(std::forward<T>(t));
}

当传递给 sender 的是一个右值——比如 10 时,推断出来的 T 是一个普通类型即 int,传给 forward 的形参 arg 的实参就是一个 int,从而推出 Type 是 int,这时调用 std::forward<int>(t) 返回的是 int&&,保存了原实参的右值属性。

当传递给 sender 的是一个左值——比如 j 时,这时我们推断出来的 T 应该是一个 int& 了,调用 std::forward<int&>(t) 返回 int & &&(无效代码,演示)折叠成为 int&,保存了原实参的左值属性。

 

转载于:https://blog.youkuaiyun.com/qq_38216239/article/details/80815142

 

(SAW:Game Over!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值