左值、右值和移动语义
左值和右值
C++的表达式分为左值表达式和右值表达式,右值又分为纯右值和将亡值。
左值和右值的定义:
- 指代非临时对象且不可移动的表达式被称为左值表达式;当对象被用左值时,被使用的是对象的身份(在内存中的位置)。
- 指代非临时对象且可移动的表达式被称为将亡值表达式;当对象被用作将亡值时,被使用的是对象的值(内容)。
- 指代临时对象且可移动的表达式被称为纯右值表达式;做纯右值时被使用的是对象的值。
常见的左值:
- 赋值运算符的左侧运算对象为左值,得到的结果也是左值。
- 内置解引用运算符(*),下标运算符([]),迭代器解引用运算符,string和vector的下标运算符的求职结果都是左值。
- 内置类型和迭代器的递增递减运算符作用于左值运算对象。
常见的右值:
- 对一个左值运算对象取地址返回一个右值。
- 字面量(字符字面量除外,字符字面量在内存中有自己的地址)。
移动语义
我们在编写程序时经常会拷贝对象,被拷贝的对象体量大或是对象本身要求分配内存空间时会造成很大的开销,并且有时在拷贝后不需要先前的对象,这是对象拷贝就显得很没有必要了,由此在C++11中引入了对象移动。
右值引用和左值引用
对象移动具体是通过右值引(&&)用实现的,右值引用就是必须绑定到右值上的引用,与之相对的是左值引用(也称为常规引用)。
当使用拷贝构造或拷贝赋值时,会造成拷贝可开销:
String(const String& string)
{std::cout << "Copy\n";m_Size = string.m_Size;m_Data = new char[m_Size];memcpy(m_Data, string.m_Data, m_Size);
}
但若使用移动构造,则可避免这些开销:
String(String&& string)
{printf("Move\n");m_Size = string.m_Size;m_Data = string.m_Data;string.m_Data = nullptr; //防止重复析构string.m_Size = 0;
}
使用移动赋值时要注意释放先前的空间和确保在赋值自身时不会丢失数据:
String& operator=(String&& string)
{printf("Move\n");if (this != &string) { //确保在赋值自身时不会丢失数据delete[] m_Data; //释放先前的空间m_Size = string.m_Size;m_Data = string.m_Data;string.m_Data = nullptr; //防止重复析构string.m_Size = 0;}return *this;
}
但移动构造和移动赋值只能接收右值,如当把name中的函数复制到m_Name时,不会调用移动拷贝,因为name虽然是右值引用类型,但它因有变量名而不是临时对象,被判定成左值。
Entity(String&& name):m_Name(name) {}
这时就要将其强转成右值,C++11提供了std::move函数将左值转为右值由此使用std::move即可调用到拷贝构造。
Entity(String&& name):m_Name(std::move(name)) {}