c++关于 左右值 和 左右值引用 及 函数参数(万能引用,引用折叠,forward完美转发,std::move)
左右值和左右值引用是有区别的。
左右值是指对变量类别的区分,左值是有地址的值,可以长期存在;而右值是将亡值,是临时量,没有名字。
而左右值引用是指变量的类型,如int&, int&&等,下面举一个例子:
void func(int &p) { cout << "&p" << endl; return; } void func(int &&p) { cout << "&&p" << endl; return; } int main() { int k = 1; int &lr = k; int &&rr = move(k); func(k); func(lr); func(rr); func(forward<int>(rr)); func(1); return 0; }
vs2022输出如下:
这里k被编译器推导为int &类型。至于rr的输出是&p,需注意区分左右值和左右值引用的关系。
此处rr是一个变量类型为int的右值引用的左值,因此rr作为参数和左值参数匹配。
至于后两个输出具体见下文分析。
上例中也可以看出,当函数参数类型为左值引用时,匹配左值参数,右值引用匹配右值参数。
这里可以再提一下引用折叠和万能引用的内容。
补充知识:
万能引用即模板中T&&或auto &&,必须注意当且仅当存在类型推导时&&才会被认为是万能引用。
(需注意,并非所有T&&都是万能引用,如下例:
template<class T, class Allocator = allocator<T>> //来自C++标准 class vector { public: void push_back(T&& x); … }
push_back函数的参数虽然为T&& x,但是在声明vector时,T的类型是被显示指出的,而非自动推导的,即如vector<type>x; push_back此时的参数即为type&&
)
左值和右值在模板推导时是存在差异的,对于类型T的lvalue(左值),模板会推导为T&类型;但是对于类型T的右值,模板会推导为T。
在模板函数中,模板参数类型由编译器去自动匹配时,左值会匹配为t&,右值会匹配为t&&
using namespace std; template<typename T> void print(T& value) { cout << "左值" << endl; } template<typename T> void print(T&& value) { cout << "右值" << endl; } template<typename T> void func(T&& value) { print(value); print(forward<T>(value)); } int main() { int k = 1; int &lr = k; int &&rr = move(k); cout << "k" << endl; func(k); cout << "lr" << endl; func(lr); cout << "rr" << endl; func(rr); cout << "1" << endl; func(1); return 0; }
vs2022输出如下:
这里逐一分析一下。对于前三者k,lr,rr,他们三者均为左值,因此编译器会推导出其类型为int &,从而T的类型为int &。
函数func的参数类型为T &&。&&之所以被称为万能引用,是因为引用折叠的原理。
对于前三者k,lr,rr,T=int &,T &&=int & &&=int &。因此该处函数参数为左值引用,而传入参数又为左值,故匹配,func可被正常调用。
对于常数1,其是右值,T=int。(这里需注意万能引用绑定到右值模板会推导为T而非T&&),因此T&&=int &&,(注意此处未发生引用折叠)。因此参数类型为int&&可正常匹配右值1.
所以说左右值都可以匹配函数func,故而称模板参数T&&为万能引用。
再看程序输出,k,lr,rr均为左值,到函数内部也是左值,因此输出均为左值。
1是右值,匹配到函数内,参数value获取了该右值,但是对于value来说,其是一个类型为右值引用的左值,因此直接将其作为print的参数会输出左值。
但是使用forward函数转发,forward函数会根据参数类型决定返回值类型,若参数为右值或右值引用,则会返回右值,反正返回左值,因此得到的返回值是右值。
std::move可将实参转化为右值,其源码为:
//c++11 template<typename T> //在std命名空间 typename remove_reference<T>::type&& move(T&& param) { using ReturnType = //别名声明 typename remove_reference<T>::type&&; return static_cast<ReturnType>(param); }
从源码可看出其返回的对象是一个右值引用,这里再提一下右值引用函数和左值引用函数。
若函数返回值为值传递,那么函数返回的参数一定为右值。
若函数返回值为左值引用,那么函数返回的参数一定为左值。
若函数返回值为右值引用,那么函数返回的参数一定为右值。
因此,std::move可以返回一个类型为右值的参数,期间不发生任何拷贝。
无论是std::move还是std::forward,他们都对原参数不做任何修改,仅仅做了转发。发生修改的过程往往在移动或拷贝构造函数等过程中。
EffectiveModernCpp中条款25指出
对右值引用使用std::move
,对通用引用使用std::forward
本文为个人理解,仅供参考,若有错误欢迎指出
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)