移动语义的产生
值分左右
- lvalue: 等号左边的表达式,既有名字又有地址的表达式
变量,函数,数据成员名字
左值引用的表达式 如++x ,x=1 ,cout<<' '
字符串字面量
int & operator==(const int &lhs);
ofstream & operator <<(ofstream on,const char *);//使用const char* 避免string 类型构造
这些表达式背后都将返回一个左值引用
- rvaue: 等号右边的表达式
- glvalue: generalized lvalue 广义左值
- xvalue: expiring lvalue 将亡值
- prvalue:pure ravlue 纯右值
纯右值没有标识符,是不可取地址的表达式,一般为临时对象
返回非引用类型 x++,x+1,make_shared<int>(42)
除字符串的字面量。
右值可以绑定在常量引用上(const int &x=1) 而不能绑定在普通引用(int &)
const与volatile作用
static静态变量和全局变量和const char* 类型存储在只读代码区,不可修改修改将产生错误
const修饰的变量不是一个真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边
volatile强制编译器减少优化,必须每次从内存中取值 ,申明变量可变
它可理解为“编译器警告指示字”,告诉编译器必须每次去内存中取变量值,主要修饰可能被多个线程访问的变量同时也可以修饰可能被未知因数更改的变量
移动语义
type a;
type b=std::move(a);
如何理解其中语义
move强制将一个左值引用改变为一个右值引用,std::move(a)等价于 static_cast<type&&>(a),转换为一个引用值。
通过move的变量为xvalue为将亡值,都是右值
move语义对生命周期的影响
C++ 的规则是:一个临时对象会在包含这个临时对象的完整表达式估值完成后、按生成顺序的逆序被销毁
如果一个 prvalue 被绑定到一个引用上,右值引用的生命周期将延续到和引用变量一样长。
++i;
++i 返回的值将在这条语句结束后被释放
int && a ==++i;
生命周期将延续与a相同。
但是通过move生成的xvlue将不会改变生命周期
移动语义的意义
解决在效率上的问题,减少不必要的拷贝。如果存在这样一个移动构造函数的话,所有源对象为临时对象的拷贝构造行为都可以简化为移动式(move)构造。对于普通的string类型而言,std::move和copy construction之间的效率差是节省了一次O(n)的分配操作,一次O(n)的拷贝操作,一次O(1)的析构操作(被拷贝的那个临时对象的析构)。这里的效率提升是显而易见且显著的。
在STL vector扩容时,移动语义的优势更为重要,提升了很大的效率。
引用折叠与完美转发
在泛型编程中,引用折叠很容易出现。
template<typename T>
foo(T &&)
如果传入类型为左值 推导结果为左值引用
如果传入类型为右值 推导结果为T本身
如果T为左值引用,那么T & && 会折叠为T &
如果T为实际类型,那T&& 的结果自然是一个右值引用
1.所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)
完美转发
template <typename T>
void bar(T&& s)
{
foo(std::forward<T>(s));
}
很多标准库里的函数,连目标的参数类型都不知道,但我们仍然需要能够保持参数的值类别:左值的仍然是左值,右值的仍然是右值。这个功能在 C++ 标准库中已经提供了,叫 std::forward。
T&& 的作用主要是保持值类别进行转发,它有个名字就叫“转发引用”(forwarding reference)。因为既可以是左值引用,也可以是右值引用,它也曾经被叫做“万能引用”(universal reference)。