左值与右值
左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式,一般认为:可以放在=左边的,或者能够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值,但是这并不是完全准确。
比如:
a,b都是左值,b既可以放在=左侧也可以放在右侧。
int a = 10;
int b = 20;
a=b;
b=a;
左值与右值很难区分,一般认为:
- 普通类型的变量,可以取地址,都认为是左值。
- const修饰的常量,不可修改,只读类型的,理论上应该按照右值对待,但因为其可以取地址,C++11认为其是左值。
- 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
- 如果表达式运行结果或单个变量是一个引用则认为是左值。
c++11中的右值
C++11对右值进行了严格的区分:
C语言中的纯右值。
比如:a+b, 100
将亡值。
比如:表达式的中间结果、函数按照值的方式进行返回。
右值引用的作用
首先我们来看深拷贝和浅拷贝
class Vector{
int num;
int* a;
public:
void ShallowCopy(Vector& v);
void DeepCopy(Vector& v);
};
浅拷贝
按位拷贝对象,创建的新对象有着原始对象属性值的一份精确拷贝(但不包括指针指向的内存)。
void Vector::ShallowCopy(Vector& v){
this.num = v.num;
this.a = v.a;
}
深拷贝
拷贝所有的属性(包括属性指向的动态分配的内存)。换句话说,当对象和它所引用的对象一起拷贝时即发生深拷贝。
void Vector::DeepCopy(Vector& v){
this.num = v.num;
this.a = new int[num];
for(int i=0;i<num;++i){a[i]=v.a[i]}
}
可以看到,深拷贝的开销往往比浅拷贝大,所以我们就倾向尽可能使用浅拷贝。
浅拷贝存在的问题
当有指向动态分配内存的属性时,会造成多个对象共用这块动态分配内存,从而可能导致冲突。
一个可行的办法是:
每次做浅拷贝后,必须保证原始对象不再访问这块内存(即转移所有权给新对象),这样就保证这块内存永远只被一个对象使用。
那有什么对象在被拷贝后可以保证不再访问这块内存呢?
很容易就想到了将亡值。
移动语义
这里的将亡值也就是上面说的右值,使用右值引用可以有效地缓解内存的压力。
在C++11提出了移动语义概念。将一个对象中资源移动到另一个对象中的方式。
而这里的移动语义必须使用右值引用来完成。
//移动构造函数
Vector::Vector(Vector&& temp){
this.num = temp.num;
this.a = temp.a;
temp.a = nullptr;
}
移动语义的作用
- 可以有效的缓解内存的压力
- 提高程序的运行效率,因为不需要开辟空间和释放空间
- 移动构造函数参数不能加const类型的右值引用,因为资源无法转移而导致语义失效
- 在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此类中涉及到资源管理时,用户必须显示定义自己的移动构造
- 在C++11中,拷贝构造/移动构造/赋值/移动赋值函数必须同时提供,或者同时不提供,程序才能保证类同时具有拷贝和移动语义