C++完美转发(详解,适合小白)

        我们知道,C++中有左值引用和右值引用,首先我们要知道什么是左值什么是右值。

        左值:表达式结束后依然存在的持久对象。左值可以出现在赋值语句的左边或右边。例如,变量和函数返回的引用都是左值。左值通常有持久的地址,可以被取地址操作符 & 获取地址。

        右值:通常是临时的,不能有多个引用,它们不指向持久存储。右值可以出现在赋值语句的右边,但不能出现在左边。字面量(如 42 或 "hello")和将亡值(函数返回的非引用类型)都是右值。右值通常用于描述临时对象或字面值,它们在使用后很快就会被销毁。

        那么,在将左值引用和右值引用传入函数的时候,我们有时候需要保存它的左值属性或者右值属性,这时就需要分别写出左值引用和右值引用函数重载。我们也可能需要将不同类型的参数传入函数,这时候就需要用到函数模板。

函数重载如下:

void func(int &n){
    cout << "左值=" << n << endl;
}

void func(int &&m){
    cout << "右值=" << m << endl;
}

函数模板如下:

//第一种函数模板
template<class T>
void func(T &n){
    cout << "左值=" << n << endl;
}
template<class T>
void func(T &&m){
    cout << "右值=" << m << endl;
}


//第二种函数模板
void func(int &n){
    cout << "左值=" << n << endl;
}
void func(int &&m){
    cout << "右值=" << m << endl;
}
void func(double &n){
    cout << "左值double=" << n << endl;
}
void func(double &&m){
    cout << "右值double=" << m << endl;
}

template<class T>
void revoke(T &&t){
    func(t);
}

        第一种能正确输出左右值,但是,它并不能对不同类型的参数调用不同操作(不符合多态),假如你想要int类型的参数打印int,想要double类型的参数打印double,第一种函数无法实现。

        第二种不能正确输出左右值,全部都走到左值。因为在函数调用时,如果你直接传递一个具名变量(无论它是通过左值引用还是右值引用声明的),该变量作为参数传递给函数时总是作为左值传递的。

        综上,只有用完美转发才能实现既识别左右值,又能对不同类型的参数调用不同操作。

通过forward<>()实现完美转发

void func(int &n){
    cout << "左值int=" << n << endl;
}
void func(int &&m){
    cout << "右值int=" << m << endl;
}
void func(double &n){
    cout << "左值double=" << n << endl;
}
void func(double &&m){
    cout << "右值double=" << m << endl;
}

template<class T>
void revoke(T &&t){
    func(forward<T>(t));
}

int main(){
    int i = 10;
    int &n = i;
    int &&m = 100;
    m = 10;

    //只能识别左右值,不能根据参数类型选择不同操作
    // func(n);
    // func(static_cast<int&&>(m));


    //正确推导
    revoke(static_cast<int&>(n));
    revoke(static_cast<int&&>(m));
    revoke(n);
    revoke(move(m));    //移动语义,将m的内容移动给一个临时变量,此时传递的是将亡值,会被识别成右值。move之后m的资源被转移,不应该再次使用m

    double d = 1.1;
    double &dn = d;
    double &&dm = 10.10;
    revoke(static_cast<double&>(dn));
    revoke(static_cast<double&&>(dm));

    // //err -- 全部是左值
    // revoke(n);
    // revoke(m);

    // //err -- 全部是右值
    // revoke(static_cast<int>(n));
    // revoke(static_cast<int>(m));
    return 0;
}

输出如下:

走到这里,又引出三个问题。1.T&&是什么        2.forward原理        3.为什么要强转

1.T&&是什么:

        有个东西叫引用折叠

                引用折叠的规则:

                        如果两个引用中至少有一个是左值引用(&),那么结果是左值引用(&

                        如果两个引用都是右值引用(&&),那么结果是右值引用(&&

                比如我们int & &&,那么它会引用折叠变成int & 

                               int && &&,会变成int &&

        引用折叠和模板很像,模板识别参数类型,引用折叠识别引用类型。比如我们template<class T>,那么输入int就会识别成int,输入double就会识别成double。在引用折叠下,输入什么值引用就会识别成什么值引用。

2.forward原理

        forward用于保持原参数的类型,比如我是int&就保持int&,在第二种函数模板中,我们已经说过了,如果不保持原参数的类型,那么t就是一个左值变量,会全部走到左值

3.为什么要强转

        查看完美转发的代码,可以发现有两个错误示例

    //err -- 全部是左值
    revoke(n);
    revoke(m);

    //err -- 全部是右值
    revoke(static_cast<int>(n));
    revoke(static_cast<int>(m));

        第一个 -- 在函数调用时,如果你直接传递一个具名变量(无论它是通过左值引用还是右值引用声明的),该变量作为参数传递给函数时总是作为左值传递的。如果我们不强转,那么会被识别左值,这里的强转实际上是告诉函数,我想要传递的类型是什么

        第二个 -- 当一个A类型变量强转为B类型时,实际上是创建了一个B类型的匿名对象来接收一个A类型的变量的值,而匿名对象是右值(将亡值),并且它不具名,所以会被识别成右值(只有具名变量才会默认作为左值传递)

        

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值