左右值2
1 左值指的是可以取地址的变量,右值指的是非左值.
二者的根本区别在于能否获取内存地址,能否赋值不是区分的依据.
通常临时量均为右值.
2 C++03标准中只有const左值引用与non-const左值引用.
a) const左值引用即常量左值引用,表现形式为:cosnt T &
b) non-const左值引用即非常量左值引用,表现形式为:T &
3 非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值.原因如下:
a) 如果非常量左值引用允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,
这明显违反了其常量的含义.
b) 非常量左值引用绑定到非常量右值,是c++所不允许的.因为去改变一个临时量的值没有任何意义.
4 常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值.
5 临时变量(右值)生命周期
a) 对于 const int& ival = Foo(); 而言,函数Foo返回的临时量会在ival死的时候才死.
即:如果存在对临时对象的引用,临时对象的生命周期与引用该临时变量的引用的生命周期相同.
b) 小结:
1. 临时对象应该在完整表达式结束时销毁
2. 常量左值引用会延长临时变量的生命
6 4个变量或者表达式,如下:
a) string one("foo"); 非const左值
b) const string two("bar"); const左值
c) string three() { return "three"; } 非const右值
d) const string four() { return "four"; } const右值
以上四个变量,只有变量one可以接受修改语义,其余三个只能接受常量语义.
实际上就是前面3,4所讲的,只有非常量左值是可以被修改的,常量左值和常量右值当然不能被修改,
而非常量右值也是属于右值,不能对其进行修改.
需要注意的是:如果同时提供const引用和非const引用版本的重载,
那么one会优先选择更加符合自身的非const引用,
其他三个变量只能选择const引用.
上面反应了C++重载决议的一个特点:参数在通用参数和更加符合自身的参数之间,优先选择后者.
附注:引用不是数据类型,而是一种属性.
7 C++11引进了右值引用,其带来的变化主要是:移动语义(move semantics)和 完美转发(perfect forward).
8 参数接收情况
a) const X&可以接受所有的参数.
b) X&只可以接收非const左值.
c) X&&只可以接收非const右值.
d) const X&&可以接受右值.
当提供了四个重载版本时,
one(非const左值)选择X&, two(const左值)选择const X&,
three(非const右值)选择X &&,
four(const右值)选择const X&&.
9 只有X&&类型(非常量右值)的变量可以被移动(move)
10 当提供如下三个版本的重载时,
a) void test(const string &s) 常量语义
b) void test(string &s) 修改语义
c) void test(string &&s) 移动语义
对于函数重载决议而言,
第一个可以接受const左值和const右值.
第二个接受非const左值,
而最后一个接受非const右值.
提供这三个版本的目的就是为了区分各种语义.
11 在C++11中,一个类应该提供的构造函数有:
a) Test();
b) Test(const Test &) 拷贝构造函数 (常量语义)
c) Test(Test &&) 移动构造函数 (移动语义)
一个类应该提供的赋值操作符有:
a) Test &operator=(const Test &); 复制赋值运算符 (常量语义)
b) Test &operator=(Test &&); 移动赋值运算符 (移动语义)
-----------------分割线-------------------------------------------
一般如果不是很奇葩的程序,我们无需为构造函数和赋值运算符提供修改语义,即:
Test(Test &) 重载拷贝构造函数 (修改语义)
Test &operator(Test &) 重载拷贝赋值运算符 (修改语义)
12 std::move的作用是将一个变量强制转化为右值引用.move会导致原来的变量失效(原来变量占有的资源已被窃取).
13 在第10点提到有三种语义,如下:
a) void test(const string &s) 常量语义
b) void test(string &s) 修改语义
c) void test(string &&s) 移动语义
参数形式为const string &name(常量语义)和string &&name(修改语义)的,可以合并为(string name),
但是后者需要在函数中显式的调用move函数.
我们来计算下开销:
a) const string &name(常量语义)中,将实参绑定到name不需要复制开销,但在函数体中用name来复制时,需要复制开销
形参形式为string name时,实参复制需要一次复制开销,但是在函数体中,可以使用std::move(name)来偷取资源,进行移动构造
即两者均为一次复制开销
b) string &&name(移动语义),显然绑定实参不需要复制开销(引用),然后在函数体中也是移动构造,整个过程没有复制开销
形参形式为string name时,实际上实形结合时,也是移动构造,在函数体中不用说,也是移动构造,整个过程也没有复制开销
14 最后需要注意:C++98没有区分“copy”和“move”语义.
15 总结:对对象进行构造或者赋值时,对于非常量右值(非const的临时量),尽量使用“移动”,这样可以避免拷贝与赋值的开销.