右值是C++11中新出现的特性,可以提高性能,减少拷贝开销。
右值语义
有移动构造与赋值的类示例:
class MyString
{
public:
MyString() : m_pData(NULL), m_nLen(0)
{
cout << "Default constructor" << endl;
}
MyString(const char *pStr) // 允许隐式转换
{
cout << "Constructor(const char *)" << endl;
m_nLen = strlen(pStr);
CopyData(pStr);
}
MyString(const MyString &other)
{
cout << "Assign constructor(&)" << endl;
if (!other.m_pData)
{
m_nLen = other.m_nLen;
DeleteData();
CopyData(other.m_pData);
}
}
MyString(MyString &&other)
{
cout << "Move constructor(&&)" << endl;
m_nLen = other.m_nLen;
m_pData = other.m_pData;
other.m_pData = NULL;
}
MyString &operator=(const MyString &other)
{
cout << "Assign operator=(&)" << endl;
if (this != &other)
{
m_nLen = other.m_nLen;
DeleteData();
CopyData(other.m_pData);
}
return *this;
}
MyString &operator=(MyString &&other)
{
cout << "Move operator=(&&)" << endl;
if (this != &other)
{
m_nLen = other.m_nLen;
m_pData = other.m_pData;
other.m_pData = NULL;
}
return *this;
}
void Output() const
{
cout << "Output: " << (m_pData ? m_pData : "<NULL>") << endl;
}
~MyString()
{
cout << "Destructor: " << (m_pData ? m_pData : "<NULL>") << endl;
DeleteData();
}
private:
void CopyData(const char *pData)
{
if (pData)
{
m_pData = new char[m_nLen + 1];
memcpy(m_pData, pData, m_nLen);
m_pData[m_nLen] = '\0';
}
}
void DeleteData()
{
if (m_pData != NULL)
{
delete[] m_pData;
m_pData = NULL;
}
}
private:
char *m_pData;
size_t m_nLen;
};
MyString Fun()
{
MyString str = "hello world";
return str;
}
void funRef(const MyString &str)
{
str.Output();
}
void funMove(MyString&& str){
auto str1 = std::move(str); // 必须使用move才会引发移动构造
str.Output();
str1.Output();
}
int main()
{
auto str = Fun();
cout<<"####To call Ref"<<endl;
funRef(str);
cout<<"####To call Move"<<endl;
funMove(std::move(str));
return 0;
}
// Constructor(const char *)
// ####To call Ref
// Output: hello world
// ####To call Move
// Move constructor(&&)
// Output: <NULL>
// Output: hello world
// Destructor: hello world
// Destructor: <NULL>
深浅拷贝
对象拷贝时,会涉及到浅拷贝(shallow copy)和深拷贝(deep copy):
- 浅拷贝:按位拷贝对象,新的对象与原对象属性值精准相同(指针也指向同一块内存);
- 深拷贝:拷贝所有的属性(若是指针,则申请一块新的内存);
一般情况下,深拷贝比浅拷贝开销大;但浅拷贝存在一个严重的问题:当有指针时,会造成多个对象共用一块内存,导致冲突。
但是若能保证原对象不再访问这块内存(如临时对象),则就不存在冲突了。
左值与右值
C++很早就有左值与右值的概念:
- 左值
lvalue
(loactor value,可寻址的数据):表达式结束后依然存在的对象; - 右值
rvalue
(read value,可获取值的数据):表达式结束后不再存在的对象;
字面量(字符字面量除外,其存放在静态存储区,持久存在)、临时表达式、临时的函数返回值等都是右值。一般情况下,有变量名的对象为左值,无变量名的为右值。
右值引用
为匹配右值,C++11中引入了右值引用类型&&
;并增加了移动构造、赋值函数(移动,即转移所有权,表明原对象是个临时对象)。
为把某些左值(如很快会离开作用域的左值)匹配成右值,提供了std::move<T>
函数,把左值强制转换为右值,以匹配右值引用类型。
纯右值与将亡值
C++11中对左值、右值类别进行了重新定义;每个C++表达式只属于基本类别中的一种:
- 左值(lvalue)表达式:拥有身份(非临时对象)且不可被移动的表达式;
- 将亡值(xvalue)表达式:拥有身份,但可被移动(可被右值引用类型匹配)的表达式;
- 纯右值(prvalue)表达式:不拥有身份且可被移动的表达式(纯粹的临时对象);
函数
函数参数与返回值也会涉及到右值优化的问题:
- 首先要编写类型的移动构造与赋值函数,让参数自然而然地享受右值移动的优化效果;
- 若参数支持移动构造,在提供左值引用的同时可提供右值引用版本(移动开销很低时,可除外);
- 不要编写返回右值引用类型的函数(特殊情况除外)。
万能引用
万能引用(Universal Reference):
- 发送类型推导(如模板、auto)时,使用
T&&
表示万能引用;其他情况表示右值引用; - 万能引用类型的形参能匹配对应类型的左值与右值。
注意:万能引用参数的函数为贪婪函数,容易让需要隐式转换的实参匹配到不希望的转发引用函数。
如下:非int类型不会因隐式转换而去匹配fun(int),而是直接匹配了模板引用类型。
template<class T> void fun(T&& value);
void fun(int v);
short s=0;
int i=0;
long l=0;
fun(s); // 匹配模板 &&
fun(i);
fun(l); // 匹配模板 &&
引用折叠
当出现引用嵌套时,C++会通过引用折叠规则保证要么是左值,要么是右值:
引用折叠 | & | && |
---|---|---|
& | & | & |
&& | & | && |
完美转发
使用万能引用即可以同时匹配左值、右值;但需要转发参数给其他函数时,会丢失引用性质(形参是个左值,无法判断到底匹配的是个左值还是右值)。为此需要完美转发函数std::forward<T>
将参数类型保持原样传入,forward一般用于模板函数:
template<typename T>
void func(T&& val){
realFun(std::forward<T>(val));
}
类型转换
C++11标准中新增了四个新的类型转换操作符:
- const_cast:用于增加或去除const、volatile修饰;
- static_cast:可显式执行所有编译器可执行的隐式类型转换操作(但不能执行多态类的交叉转换);
- dynamic_cast:用于多态对象间(从基类到派生类的向下转换,从一个基类到另一个基类的交叉转换)的类型转换;
- reinterpret_cast:对目标的内存二进制位进行低层次的重新解释(类似老式C语言的显示类型转换);
dynamic_cast 引用类型转换失败时会抛出bad_cast异常;而指针转换失败时则返回空指针(nullptr);