1. 程式人生 > >右值引用與move語義

右值引用與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);
}