先说结论:右值引用,就是为了移动语义。移动语义就是为了减少拷贝。std::move就是将左值转为右值引用。这样就可以重载到移动构造函数了,移动构造函数将指针赋值一下就好了,不用深拷贝了,提高性能
1.左值右值:可以取地址的为左值,不可以取地址的则为右值
int a = 5; //a是左值,5是右值
int b = a; //b和a都是左值
int a = fun();//函数的返回值也是右值
2.左值引用,右值引用:顾名思义就是对左值和右值取引用(左值引用和右值引用都是左值)
左值引用只能取左值,用const修饰除外:
int a = 5;
int &ref_a = a; // 左值引用指向左值,编译通过
int &ref_a = 5; // 左值引用指向了右值,会编译失败
const int &ref_a = 5; // 编译通过
右值引用专门为右值而生,可以指向右值,不能指向左值:
int &&ref_a_right = 5; // ok
int a = 5;
int &&ref_a_left = a; // 编译不过,右值引用不可以指向左值
ref_a_right = 6; // 右值引用的用途:可以修改右值
3.右值引用指向左值:使用std::move
int a = 5; // a是个左值
int &ref_a_left = a; // 左值引用指向左值
int &&ref_a_right = std::move(a); // 通过std::move将左值转化为右值,可以被右值引用指向
cout << a; // 打印结果:5
4.移动语义:
- 移动:即转移对象所有权,是将对象原有的内存转交给新对象,内存地址不会发生改变,拷贝是在新的地址对对象进行复制,内存地址是新的地址。
- 在实际场景中,右值引用和std::move被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。 在没有右值引用之前,一个简单的数组类通常实现如下,有
构造函数
、拷贝构造函数
、赋值运算符重载
、析构函数
等。深拷贝/浅拷贝在此不做讲解。
class Array {
public:
Array(int size) : size_(size) {
data = new int[size_];
}
// 深拷贝构造
Array(const Array& temp_array) {
size_ = temp_array.size_;
data_ = new int[size_];
for (int i = 0; i < size_; i ++) {
data_[i] = temp_array.data_[i];
}
}
// 深拷贝赋值
Array& operator=(const Array& temp_array) {
delete[] data_;
size_ = temp_array.size_;
data_ = new int[size_];
for (int i = 0; i < size_; i ++) {
data_[i] = temp_array.data_[i];
}
}
~Array() {
delete[] data_;
}
public:
int *data_;
int size_;
};
该类的拷贝构造函数、赋值运算符重载函数已经通过使用左值引用传参来避免一次多余拷贝了,但是内部实现要深拷贝,无法避免。 这时,有人提出一个想法:是不是可以提供一个移动构造函数
,把被拷贝者的数据移动过来,被拷贝者后边就不要了,这样就可以避免深拷贝了:
class Array {
public:
Array(int size) : size_(size) {
data = new int[size_];
}
// 深拷贝构造
Array(const Array& temp_array) {
...
}
// 深拷贝赋值
Array& operator=(const Array& temp_array) {
...
}
// 移动构造函数,可以浅拷贝
Array(const Array& temp_array, bool move) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}
~Array() {
delete [] data_;
}
public:
int *data_;
int size_;
};
这么做有2个问题:
- 不优雅,表示移动语义还需要一个额外的参数(或者其他方式)。
- 无法实现!
temp_array
是个const左值引用,无法被修改,所以temp_array.data_ = nullptr;
这行会编译不过。当然函数参数可以改成非const:Array(Array& temp_array, bool move){...}
,这样也有问题,由于左值引用不能接右值,Array a = Array(Array(), true);
这种调用方式就没法用了。
可以发现左值引用真是用的很不爽,右值引用的出现解决了这个问题,在STL的很多容器中,都实现了以右值引用为参数的移动构造函数
和移动赋值重载函数
,或者其他函数,最常见的如std::vector的push_back
和emplace_back
。参数为左值引用意味着拷贝,为右值引用意味着移动。
class Array {
public:
......
// 优雅
Array(Array&& temp_array) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}
public:
int *data_;
int size_;
};
如何使用:
// 例1:Array用法
int main(){
Array a;
// 做一些操作
.....
// 左值a,用std::move转化为右值
Array b(std::move(a));
}