• C++11(二)右值引用&&移动语义


    🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
    在这里插入图片描述

    左值引用和右值引用

    之前学过的引用几乎都是左值引用,但是无论是左值引用还是右值引用,都是给对象取别名

    1.左值与左值引用

    左值可以是变量或者表达式,左值引用的真正特点就是能够去取它的地址,本来也应该算上可以赋值这个特点,但是const修饰的左值不可以,所以可以取地址的就是左值

    那么左值引用就是对左值取别名

    int a;//左值
    int &b=a;//左值引用
    
    • 1
    • 2

    左值既可以出现在等号左边也可以出现在赋值符号右边

    2.右值与右值引用

    右值也是一个变量或者表达式,右值引用只能出现在赋值符号右边,因为它不能够被取地址,所以无法被赋值。右值一般就是字面量和表达式返回值的临时对象

    double x=1.1,y=2.2;
    //以下的都是右值
    10;
    x+y;//字面量
    fmin(x,y);//返回值的临时对象
    
    • 1
    • 2
    • 3
    • 4
    • 5

    右值引用:对右值取别名,右值引用是两个&&

    double x=1.1,y=2.2;
    //以下的都是右值
    10;
    x+y;//字面量
    fmin(x,y);//返回值的临时对象
    
    //右值引用
    int&& rr1=10;
    double&& rr2 = x+y;
    double&& rr3 = fmin(x+y);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.左值引用和右值引用比较

    左值引用引用右值

    一般左值引用是不能给右值引用取别名的,因为左值引用的表达式需要可以取地址和赋值

    double&l = x+y/*右值*/;//报错
    
    • 1

    但是,const左值引用是可以引用右值的

    const double&r = x+y;//编译通过
    
    • 1

    因为C++11之前,没有右值引用,那怎么办呢?所以就用const左值引用的方式来引用右值,所以也推荐拷贝构造和赋值运算符重载写成const左值引用,因为不仅仅可以解决权限扩大缩小的问题,而且可以通过const左值引用来引用右值。

    右值引用引用左值

    普通的右值引用也是不能引用左值的

    int c =10;
    int && r = c;//报错
    
    • 1
    • 2

    但是,可以用move来让编译器把左值识别成右值

    int c=10;
    int &&r = move(c);
    //这样就可以了
    
    • 1
    • 2
    • 3

    4.右值引用之后的变化

    右值是不能够取地址的,但是给右值被右值引用了之后,会导致右值被存储到特定位置,且可以取到该位置的地址。也就是说:

    10;
    double x=1.1,y=2.2;
    int&rr1=10;
    const double && rr2=x+y;
    
    • 1
    • 2
    • 3
    • 4

    上面的rr1和rr2都是左值,这也会牵扯到完美转发的问题

    5.左值引用使用问题

    左值引用解决问题:
    ①做参数:a.减少拷贝,提高效率 b.做输出型参数
    ②做返回值:a.减少拷贝,提高效率 b.引用返回可以修改返回对象(比如map的operator[],修改Value)

    左值引用的问题:在函数类创建的临时对象需要返回,那只能传值返回,就做不到减少拷贝,提高效率。所以有些场景是左值引用做不到的

    如果说要求vector generate(int numRows){}>这里需要减少拷贝,怎么做?
    ①全局vector,但是全局变量会有线程安全问题,不安全
    ②new 返回vector的指针,但是忘记释放就会内存泄漏,或者用RAII的思想来搞
    ③输出型参数 void generate(int numRows,vector>&vv);
    ④利用移动构造(下面讲)

    6.移动构造

    C++把右值分为了两种
    1.内置类型的右值 – 纯右值
    2.自定义类型的右值 – 将亡值(通常都是一些临时对象,生命周期就在这一行)

    //拷贝构造
    string(const string& s)
    	:_str(nullptr);
    {
    	string tmp(s);
    	swap(tmp);
    }
    //移动构造
    string(string&&s)//因为s不可取地址,不可赋值,所以不加const
    	:_str(nullptr)
    {
    	swap(s);//因为s是右值,将亡,所以直接交换
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    因为有资源的转移,所以叫移动构造

    
    string str1(move(str1));
    string str2(string("hello"));
    
    • 1
    • 2
    • 3

    移动构造比拷贝构造的代价小太多,拷贝构造需要先构造一个临时变量(而这个构造是深拷贝,当然也可以用引用计数和写时拷贝来进行优化),然后将这个临时变量再拷贝构造给原来的变量。而移动构造在函数内部就资源转移了,只是交换了一下管理的指针。

    编译器也是很聪明的,有const左值引用的拷贝构造和右值引用的移动构造,都可以引用右值,但是确是调用的移动构造,因为它能够识别到这是一个将亡值。挑选最匹配的去调用

    7.移动赋值

    string ret;
    ret = to_string(1234);
    //这时候就需要移动赋值
    
    • 1
    • 2
    • 3
    //拷贝赋值
    string& operator=(const string&s)
    {
        string(tmp);
        swap(tmp);
        return *this;
    }
    
    //移动赋值
    string& operator=(string&&s)//构造
        swap(s);//s将亡
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8.STL变化

    C++11以后,STL就提供了移动构造和移动赋值
    右值引用除了移动构造和移动赋值以外,insert也用到了
    在这里插入图片描述
    还有就是增加了emplace
    在这里插入图片描述
    传递多个左值/右值(因为会引发万能引用/引用折叠)来进行插入

    9.完美转发

    先来看一个现象
    在这里插入图片描述
    右值引用&&和模板在一起,就会引发一个东西叫做 万能引用/引用折叠
    引用折叠的意思就是传左值过去的时候,会把&&折叠成&,以对左值进行兼容(有了引用折叠,其实就可以替代单纯的左值引用,但是为了向前兼容,左值引用还存在)

    然后再上面也说过,右值引用会把右值存在一个特定的位置,导致右值的别名是一个左值(因为可以对这个别名取地址)
    所以上面就可以看到都是左值引用的结果

    C++11中就提供了一个东西—>完美转发
    在这里插入图片描述
    可以让别名t保持它原有的属性,就可以做到去调用右值引用
    完美转发的意思就是你把我给你的东西原封不动的交给别人,不要改变它的属性

    在一个调用链中连续调用就会用到,比如说list的push_back

    void push_back(const T& x)
    {
    	//    ...    
    }
    void push_back(T&&x)//引用折叠,既可引用左值也可引用右值
    {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    push_back调用的是insert,如果不进行完美转发,那么push_back传给insert的就是左值,然后就又会去调用拷贝构造,并不会调用移动构造,效率也不会得到提升,在整个调用链上都需要完美转发一下

    10.默认移动构造和移动赋值

    我们以前知道的默认成员函数有六种:①构造函数 ②拷贝构造函数 ③赋值运算符重载函数 ④析构函数 ⑤取地址运算符重载函数 ⑥const取地址运算符重载函数

    C++11之后,新增添了两个 ①移动构造 ②移动赋值

    编译器默认生成移动构造和移动赋值的条件

    ①如果没有实现移动构造函数,且没有自己实现析构函数,拷贝构造,拷贝赋值重载的任意一个(三个都不写才会生成,但是这三个只要有一个写了,基本都要写),那么编译器才会自动生成,并且,默认移动构造对于内置类型对象进行值拷贝,自定义类型对象,则看这个对象是否实现移动构造,如果实现就调用它的移动构造,没有实现就调用拷贝构造。
    ②移动赋值同理

    在这里插入图片描述

  • 相关阅读:
    Performance Improvements in .NET 8 & 7 & 6 -- String【翻译】
    业务中台(功能编排+领域模型的结合)-spider-node
    go 设计模式——单例模式
    Linux ARM平台开发系列讲解(Linux并发与竞争) 3.1 Linux并发与竞争概述
    【Pytorch with fastai】第 9 章 :表格建模深入探讨
    SpringBoot+Redis BitMap 实现签到与统计功能
    淘宝/京东/拼多多/苏宁/抖音等平台详情数据分析接口(APP商品详情源数据接口代码对接教程)
    一克商评|未来向外输出自动驾驶技术和解决方案的中国企业会越来越多
    含文档+PPT+源码等]基于ssm maven健身房俱乐部管理系统[包运行成功]Java毕业设计SSM项目源码
    1543_AURIX_TC275_CPU子系统_CPU内核实现特性
  • 原文地址:https://blog.csdn.net/zhu_pi_xx/article/details/128077541