右值引用與move語義
右值
C語言中,左值(left value, lvalue)只出現在賦值符左邊的量,右值(right value, rvalue)是出現在賦值符右邊的量。在C++
中,右值的定義稍微不同,每一個表示式都會產生一個左值或者右值,所以表示式也稱左值表示式或右值表示式。
- 對於基礎型別,右值不可修改,也不可被const,volatile修飾
- 對於自定義型別,右值可以被成員函式修改
class Foo
{
public:
Foo(int i) : m_i(i) {}
void setI(int i) { m_i = i; }
private:
int m_i;
};
Foo getFoo() //返回右值,具有臨時性
{
return Foo(0);
}
int main()
{
getFoo().setI(2); //呼叫右值的成員函式,可以修改
Foo *f = &getFoo(); //error: taking address of temporary
return 0;
}
非常量右值引用
只能繫結到非常量右值,不能繫結到非常量左值、常量左值和常量右值。
常量右值引用
可以繫結到非常量右值和常量右值,不能繫結到非常量左值和常量左值
move語義
考慮下面的一小段程式碼
vector<int> getVector()
{
vector <int> vec;
return vec;
}
vector<int> v = getVector();
上述程式碼在沒有優化的情況下,要呼叫三次建構函式,分別時構造vec,vec拷貝構造到返回值,返回值賦值構造到v。這樣子效能特別差。如果確定某個值是一個非常量右值(或者是一個以後不會再使用的左值),則我們在進行臨時物件的拷貝時,可以不用拷貝實際的資料,而只是“竊取”指向實際資料的指標。
例如上面的vec,在拷貝構造返回值時,它是一個以後絕對不會使用的左值,所以可以直接把實際資料指標賦給返回值,同理返回值賦值構造v時,返回值是一個非常量右值,也可以直接把實際資料指標賦給v。這樣子實際資料拷貝就只有一次,大大提高了效率。
C++11提供了std::move操作用於上述情形,考慮下面的程式碼:
vector<int> v{1, 2};
v = getVector();
第2句程式碼要做的工作有:
①銷燬v原先的記憶體
②將getVector返回值內容拷貝一份給v
③銷燬返回值的記憶體
如果vector定義了move賦值運算子:
template<class T>
vector<T>& vector<T>::operator=(vector<T> &&rhs);
那麼上述程式碼就會呼叫move賦值操作符,將返回值的記憶體指標賦給v。
move不僅適用於右值,可以以用於左值,std::move提供了將左值轉成右值的功能。例如使用std::move實現swap:
template <class T>
void swap(T &lhs, T &rhs)
{
T tmp(std::move(lhs));
lhs = std::move(rhs);
rhs = std::move(tmp);
}