c++关于 左右值 和 左右值引用 及 函数参数(万能引用,引用折叠,forward完美转发)

发布时间 2023-06-06 21:20:33作者: _Explosion!

左右值和左右值引用是有区别的。

左右值是指对变量类别的区分,左值是有地址的值,可以长期存在;而右值是将亡值,是临时量,没有名字。

而左右值引用是指变量的类型,如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的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函数会根据参数类型决定返回值类型,若参数为右值或右值引用,则会返回右值,反正返回左值,因此得到的返回值是右值。

 

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