C++11 - 右值引用与移动构造
前言:
Vue框架:
从项目学Vue
OJ算法系列:
神机百炼 - 算法详解
Linux操作系统:
风后奇门 - linux
左值和右值:
定义:
左值:
- 凡是可以被取地址取到的变量,都属于左值:
- 举例:
int a = 1;
int *p = new int{2};
int arr[]{1, 2, 3};
右值:
- 右值分为两类:纯右值 + 将亡值
- 纯右值:
- 常量,如"hello" / 101
- 表达式,如x+y
- 传值函数返回值,如:
int fmin(int x, int y){
return x<y?x:y;
}
- 将亡值:
-
多为局部变量,严格说函数内局部变量
-
创建后生命周期将结束的变量
-
如:该函数内所有的中间变量,在函数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;
}
-
右值特点即是无法被取到地址
其实常量也可以被视作是一种将亡值,毕竟只用到了赋值的一瞬间
自己引用:
& 和 &&:
- 我们原本使用的引用基本都属于左值引用&,C++11中新增了右值引用&&:
- 左值&引用:
int a = 0;
int &b = a;
- 右值&&引用:
int fmin(int x, int y){
return x<y?x:y;
}
int &&a = 10;
int &&b = 1+1;
int &&c = fmin(3, 5);
右值引用变量:
- 上述讲解右值引用时的用例中的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;
- 说明了右值引用变量本身其实是左值
交叉引用:
左值通过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);
- 没错,这里的const引用变量也是左值。
右值通过move()引用左值:
- move():
int x = 10;
int *p = &x;
int &&a = move(x);
int &&b = move(p);
int &&c = move(*p);
移动构造:
- 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;
}
};
- 图解深浅拷贝区别:
右值在拷贝的特殊性:
-
引入了右值(尤其是将亡值)的概念后,
我们发现对于很多再也不会使用的变量可以直接浅拷贝,
毕竟将亡值再不会被调用,所以不会造成两个对象/变量被调用的干扰问题再进一步,既然将亡值已经开辟好了内存空间,并且赋好了值
那么我们直接将指针指向这块空间,直接利用这块空间内的数据即可这样做存在一个问题:将亡值真死亡之后,其内存空间将被释放
所以我们再占用将亡值内存空间后,还需要将将亡值指针指向空,让其释放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;
}
};
编译器对中间变量的优化:
代码举例:
- 有了移动构造的概念,对于将亡值的拷贝,我们不再需要借助中间变量了:
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;
}
图示:
- 有无中间变量的拷贝过程区别: