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

本文为个人理解,仅供参考,若有错误欢迎指出

posted @   _Explosion!  阅读(142)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示