文章目录
简介
C++11 引入了右值引用,我写本篇文章的目的就是为了加深自己对左右值的理解,帮助大家在日常编程中用好左右值,也是方便自己以后复习。
本文的参考链接:链接
1,什么是左右值?
如何区分左右值有一个最简单的方法就是,左值可以取地址,位于等号左边,右值不能取地址,位于等号右边。
2,左值引用和右值引用
引用的实现类似于指针,本质是为了避免拷贝。左值引用可以指向左值,不能指向右值,但是前面加const后可以指向右值。
右值引用的标志是**&&**,可以指向右值,不能指向左值。
3,右值引用有办法指向左值吗?
可以通过std::move()将左值转化为右值,例如:
int a = 5;
int &ref_a_left = a; // 左值引用指向左值
int &&ref_a_right = std::move(a); // 通过std::move将左值转化为右值,可以被右值引用指向
move实际上就类似于一个类型的转换,单纯的std::move() 不会有性能的提升。
4,左右值引用本身是左值还是右值?
本声明出来的左值和右值引用都是左值,因为被声明出的左右值引用是有地址的,也位于等号左边。
总结
右值引用可以直接指向右值,也可以通过std::move指向左值;
而左值引用只能指向左值(const左值引用也能指向右值)。
作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。
所以在写函数的时候形参尽量使用右值引用。
5,实现移动语义
移动语义主要是为了避免深拷贝,STL类大都支持移动语义函数,即可移动的,另外,编译器会默认在用户自定义的class和struct中生成移动语义函数,但前提是用户没有主动定义该类的拷贝构造等函数。
举个例子:
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_;
};
这样实现会出现两个问题:
- 实现不够优雅,实现移动构造函数还需要两个参数
- temp_array是一个const 类型的左值,无法在函数内部进行修改。
- 如果去掉const,这样的话这个构造函数无法引用右值,就无法使用 Array a = Array(Array(),true)这种方式进行构造
6,什么时候触发移动语义?
因此,可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。
还有些STL类是move-only的,比如unique_ptr,这种类只有移动构造函数,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能拷贝(深拷贝):
std::unique_ptr<A> ptr_a = std::make_unique<A>();
//unique_ptr只有‘移动赋值重载函数‘,参数是&& ,只能接右值,因此必须用std::move转换类型
std::unique_ptr<A> ptr_b = std::move(ptr_a);
std::unique_ptr<A> ptr_b = ptr_a; // 编译不通过
7,完美转发 std::forword
因为即使使用了右值引用,在这个函数中的这个形参也还是左值,为了转发的时候保持参数的右值属性,可以使用类型转换,转换成右值。
和std::move一样,它的兄弟std::forward也充满了迷惑性,虽然名字含义是转发,但他并不会做转发,同样也是做类型转换.
std::forward最主要运于模版编程的参数转发中。
std::forward < T > (u)有两个参数:T与 u。 a. 当T为左值引用类型时,u将被转换为T类型的左值; b. 否则u将被转换为T类型右值。
例如:
#include <iostream>
#include <string>
//
template<class T>
void show_type(T t)
{
std::cout << typeid(t).name() << std::endl;
}
//
template<class T>
void perfect_forwarding(T &&t)
{
show_type(std::forward<T>(t));
}
std::string get_string()
{
return "hi world";
}
int main()
{
std::string s = "hello world";
perfect_forwarding(s);
perfect_forwarding(get_string());
}
8,forward和move的区别
std::move一定会将实参转换为一个右值引用,并且使用std::move不需要指定模板实参,而std::forward会根据左值和右值的实际情况进行转发,在使用的时候需要指定模板实参。
9,注意事项
注意: 为了能够充分发挥右值的意义,一般是要自己实现移动构造函数,编译器对于赋值源对象是右值的情况会优先调用移动赋值运算符函数,如果该函数不存在,则调用复制赋值运算符函数。