• C++11 - 2 - 右值引用与移动构造


    c++

    前言:

    Vue框架:从项目学Vue
    OJ算法系列:神机百炼 - 算法详解
    Linux操作系统:风后奇门 - linux

    左值和右值:

    定义:

    左值:

    • 凡是可以被取地址取到的变量,都属于左值:
    • 举例:
    int a = 1;
    int *p = new int{2};
    int arr[]{1, 2, 3};
    
    • 1
    • 2
    • 3

    右值:

    • 右值分为两类:纯右值 + 将亡值
    • 纯右值:
      1. 常量,如"hello" / 101
      2. 表达式,如x+y
      3. 传值函数返回值,如:
    int fmin(int x, int y){
    	return x<y?x:y;
    }
    
    • 1
    • 2
    • 3
    • 将亡值:
      1. 多为局部变量,严格说函数内局部变量

      2. 创建后生命周期将结束的变量

      3. 如:该函数内所有的中间变量,在函数return结束后都将被释放,

        函数调用结束后,无法再取得他们的值或地址

    int midfind(int l, int r, int &arr[], int target){
    	int i = l, j = r;
    	while(i < j){
    		int mid = (i + j)>>1;
    		if(arr[mid] > target)	r=mid-1;
    		else i = mid;
    	}
    	return i;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 右值特点即是无法被取到地址

      其实常量也可以被视作是一种将亡值,毕竟只用到了赋值的一瞬间

    自己引用:

    & 和 &&:

    • 我们原本使用的引用基本都属于左值引用&,C++11中新增了右值引用&&:
    • 左值&引用:
    int a = 0;
    int &b = a;
    
    • 1
    • 2
    • 右值&&引用:
    int fmin(int x, int y){
    	return x<y?x:y;
    }
    
    int &&a = 10;
    int &&b = 1+1;
    int &&c = fmin(3, 5);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    右值引用变量:

    • 上述讲解右值引用时的用例中的a b c变量都是可以取得到地址的:
    int fmin(int x, int y){
    	return x<y?x:y;
    }
    
    int &&a = 10;
    int &&b = 1+1;
    int &&c = fmin(3, 5);
    
    cout<<"&a : "<<&a<<endl;
    cout<<"&b : "<<&b<<endl;
    cout<<"&c : "<<&c<<endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 说明了右值引用变量本身其实是左值

    交叉引用:

    左值通过const引用右值:

    • C++11未出现前,左值也有引用右值的情况,

      此时需要做变量写权限的缩减,即加关键词const:

    int fmin(int x, int y){
    	return x<y?x:y;
    }
    
    int x = 1, y = 2;
    const int &a = 10;
    const int &b = x+y;
    const int &c = fmin(1, 2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 没错,这里的const引用变量也是左值。

    右值通过move()引用左值:

    • move():
    int x = 10;
    int *p = &x;
    int &&a = move(x);
    int &&b = move(p);
    int &&c = move(*p);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    移动构造:

    • C++11中,将类内默认函数由原本的6个上升到了8个:
    直接构造取地址
    析构const取地址
    拷贝构造移动构造
    拷贝赋值移动赋值

    定义:

    深浅拷贝

    • 原来我们进行直接构造和拷贝构造时,需要考虑对变量采用深浅拷贝:
    class A{
    	private:
    		int *p;
    	public:
    		//深拷贝:自己创建空间,仅作值的拷贝
    		A(A &a){
    			p = new int(*(a.p));
    			cout << "深拷贝函数" << endl;
    		}
    		//浅拷贝:自己不创建空间,直接指向原来地址
    		A(A &a){
    			p = a.p;
    			cout << "浅拷贝函数" << endl;
    		}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 图解深浅拷贝区别:
      深浅拷贝

    右值在拷贝的特殊性:

    • 引入了右值(尤其是将亡值)的概念后,
      我们发现对于很多再也不会使用的变量可以直接浅拷贝
      毕竟将亡值再不会被调用,所以不会造成两个对象/变量被调用的干扰问题

      再进一步,既然将亡值已经开辟好了内存空间,并且赋好了值
      那么我们直接将指针指向这块空间,直接利用这块空间内的数据即可

      这样做存在一个问题:将亡值真死亡之后,其内存空间将被释放
      所以我们再占用将亡值内存空间后,还需要将将亡值指针指向空,让其释放nullptr

    • 总结:
      在对右值拷贝时,鉴于右值中资源将要被释放,所以可以采用两步,直接使用右值资源
      1. 直接浅拷贝
      2. 右值指针指向nullptr

    移动拷贝/移动赋值:

    图示:

    • 所谓移动拷贝构造/移动赋值构造,指的是对将亡指中资源的移动:
      移动构造

    代码:

    • 移动构造和赋值的原理类似:基于将亡值进行类内变量构造
    class A{
    	public:
    	/*
    		移动拷贝构造:自己不创建空间,直接使用将亡值的地址空间,
    		顺便把将亡值的指针指向空,防止地址空间被释放
    	*/
    		A(A &&a){							//注意对右值的引用采用&&
            	p = a.p;
            	a.p = nullptr;
            	cout << "移动构造函数" << endl;
        	}
    
    	/*
    		移动赋值构造:其实就是用operator =重写一下移动拷贝构造
    	*/
    
    		void operator=(A &&a){					//注意对右值的引用采用&&
    			p = a.p;
    			a.p = nullptr;
    			cout<<"移动赋值构造"<<endl;
    		}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    编译器对中间变量的优化:

    代码举例:

    • 有了移动构造的概念,对于将亡值的拷贝,我们不再需要借助中间变量了:
    class A{
    	private: 
    		int* p;
    	public:
    		A(int x){
    			p = new int(x);
    		}
    };
    A func(int x){
    	A a(x);
    	return a;
    }
    int main(){
    	A b = func(1);						//C++11自带移动拷贝构造和移动赋值构造
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    图示:

    • 有无中间变量的拷贝过程区别:
      编译器对中间变量返回值的优化处理
  • 相关阅读:
    vue2升级vue3:vue2 vue-i18n 升级到vue3搭配VueI18n v9
    Pytorch 多卡并行(1)—— 原理简介和 DDP 并行实践
    多线程应用的测试与调试
    TypeError: Cannot read property '维度' of undefined at FileReader.reader.onload
    SAP 概念
    蓝桥杯刷题(六)
    数据库设计规范
    la3_系统调用(上)
    LabVIEW太赫兹波扫描成像系统
    浏览器本地存储webStroage
  • 原文地址:https://blog.csdn.net/buptsd/article/details/126872273