1. 程式人生 > >std::move的原理與實現,右值引用的深入理解

std::move的原理與實現,右值引用的深入理解

這次我真的懂了。。。。

首先C++11引入了右值引用 &&

 

‘&&’這個要連起來看,是一個整體,C++多了一個關鍵字而已。

不是引用的引用。是船新的一種語法。那有什麼用呢?

額,引數的型別又多了一種!

void fun(int T)

void fun(int& T)

void fun(int && T)

void fun(int* t)

 

之前的引數,值傳遞,引用,指標。現在呢?多了一個叫 “右值引用”的玩意,多了一種引數型別的選擇。僅此而已。

那他們號稱的右值引用速度快,代價小呢?

額,這個需要庫作者自己去實現的,跟C++語言本身無關。

 

舉兩個例子

void fun(int & t)

{

    t= 2;

}

void fun(int && t)

{

    cout<<"int &&"<<endl;

    int x = t;

   x++;

  x--;

}

這個右值引用的fun函式就更復雜了嘛,沒有說一定要簡單啊,完全由庫作者決定的。

 

當然,現在的庫作者對右值引用的函式往往做了記憶體轉移的操作(尤其是移動建構函式與移動賦值函式)

class A
{
    A(int num)
    {
        p = new int(num);
    }
    A(A& a)
    {
        p = new int(*a.p);
    }
    A(A&& a)
    {
        p = a.p;
        a.p = nullptr;
    }
    ~A()
    {
        delete p;
    }
private:
    int * p=nullptr;
};

如上,對於右值引用建構函式,僅僅是轉移了記憶體,並讓被轉移的指標置空。當然,這個右值引用建構函式具體的實現還是由庫作者決定的。

另外,如果沒有右值引用建構函式,會自動呼叫拷貝建構函式。

 

這裡說到了轉移,嗯,翻譯下就是move。move這個函式看上去是專門轉移記憶體的。實際上是錯誤的。。

move僅僅是進行了一個 右值引用 的強制轉換。

對於強制轉換,你可能會寫

template<typename T>
T && make_move(T&& t)  //當然真正的是std::move,我這裡取名實現類似的move。make_move跟make_love沒有關係哈,純粹的偶然。。
{
    return static_cast<T&&>(t);
}

額,這是啥,T &&轉換成T&& ,看上去啥都沒做嘛。

首先:對於make_move(T&& t)中的 t,說明make_move的函式引數是右值引用,但不代表t是右值引用。t可能是左值。額,越來越頭大了。

想起了“書越讀越厚,然後越讀越薄”。其實我自己對這個的理解過程也超過了2年多,這次真的搞懂了!!

上例子緩緩

int x =10;

make_move(x)  //此時x是左值,什麼叫左值,就是可以取地址的變數。&x有意義的變數。

make_move(20) //20是真正的右值。

看上去這個時候make_move體現出了意義,把t強轉成右值引用了。

但讀過  模板型別推倒、auto推導 後,我們知道,左值(或引用)的強制右值轉換返回是個左值引用。簡單的如下:

 

 

 

於是,經過make_move函式後返回的是int & 而不是int &&。

那怎麼才能得到真正的int && 呢。需要加上traits。

template<typename T>
typename remove_reference<T>::type && make_move(T&& t)
{
    using Rtype = typename remove_reference<T>::type &&;
    return static_cast<Rtype>(t);
}

typename 是為了告訴編譯器type是一個型別,這個在stl很常見。

舉個例子

struct A

{

  typedef unsigned size_t;

  static size_t value;

}

 我們訪問value      使用A::value

我們訪問size_t      使用A::size_t  那麼size_t到底是值還是型別,編譯器不明白。

所以我們會用 typename A::size_t ;  (typename 翻譯型別名字,就是表明該變數是個型別)

remove_reference<T>::type 就是去掉T的引用後的型別,再加上&&

就是真的T的右值引用了。

如你所見,這個也基本是std::move乾的事。因此move並沒有轉移記憶體還是啥的,甚至沒有轉移的語義。只是一種型別的強制轉換。

std::vector<std::string> ve;

std::string str="msg";

ve.push_back(str);

ve.push_back(std::move(str)); //內部實現可能是這樣子的

void push_back(str)  

{

    T temp (str);  //呼叫值拷貝構造

    __insert(temp);

}

ve.push_back(std::move(str));

{

  T temp (t) ;  //呼叫右值拷貝構造

    __insert(temp);

}

 

  確實會比ve.push_back(str)快一點點,std::string的右值拷貝構造直接轉移了記憶體。

 

  最終看起來像是move的功勞,也實現了轉移的語義。

  但實際上是std::string的右值拷貝構造直接轉移了記憶體。當然感謝move,但str真的從左值變成了右值引用。

 

  the  end

&n