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