Cpp / std::move 原理
零、功能和源碼
std::move 是一個類型轉換器,將左值轉換成右值,其實現如下:
template <typename T> typename remove_reference<T>::type&& move(T&& t) {return static_cast<typename remove_reference<T>::type&&>(t); }std::move 的實現還是挺簡單的就這么幾行代碼,但要理解這幾行代碼可不容易。下面我們就來對它做下詳細分析。
一、通用引用
首先我們來看一下 move 的輸入參數,move 的輸入參數類型稱為通用引用類型。什么是通用引用呢?就是它既可以接收左值也可以接收右值。我們來看一下例子:
#include <iostream>template <typename T> void func(T &¶m) {std::cout << "the value is " << param << std::endl;return; }int main(int argc, char *argv[]) {int a = 123;auto &&b = 5; //通用引用,可以接收右值//int &&c = a; //錯誤,右值引用,不能接收左值auto &&d = a; //通用引用,可以接收左值//const auto &&e = a; //錯誤,加了const就不再是通用引用了func(a); //通用引用,可以接收左值func(10); //通用引用,可以接收右值return 0; }通用引用成立條件:一種是 auto,另一種是通過模板定義的 T&&。實際上 auto 就是模板中的 T,它們是等價的。
下面我們就對這段代碼做下詳細解讀。 代碼中的 a 是個左值,因為它在內存中會分配空間,這應該沒什么異義;b 是通過引用。為什么呢?因為通用引用有兩個條件:一,必須是 T&& 的形式,由于 auto 等價于T,所以 auto && 符合這個要求;二,T 類型要可以推導,也就是說它必須是個模板,而 auto 是模板的一種變型,因此 b 是通用引用。通用引用即可以接收左值,也可以接收右值,所以 b = 5 是正確的;c 不是通用引用,因為它不符合 T&& 的形式。所經第三行代碼是錯誤的,右值引用只能接收右值;d 是通用引用,所以給它賦值 a 是正確的;e 不是通用引用,它多了一個 const 已不符合 T&& 的形式,所以給它左值肯定會出錯;最后兩個函數調用的形參符合 T&&,又因是模板可以進行類型推導,所以是通用引用,因此給它傳左值和右值它都能正確接收。
二、模板的類型推導
通用引用好強大呀!它既可以接收左值又可以接收右值,它是如何做到的呢?這就要講講模板的類型推導了。
模板的類型推導規則還是蠻復雜的,這里我們只簡要說明一下,有興趣的同學可以查一下 C++11 的規范。我們還是舉個具體的例子吧:
template <typename T> void func(ParamType param);func(expr);上面這個例子是函數模板的通用例子,其中 T 是根據?func 函數的參數推到出來的,而 ParamType 則是根據 T 推導出來的。T 與 ParamType 有可能相等,也可能不等,因為 ParamType 是可以加修飾的。我們看下面的例子:
template <typename T> void f(T param);template <typename T> void func(T ¶m);template <typename T> void function(T &¶m);int main(int argc, char *argv[]) {int x = 10; // x 是 intint &rr = x; // rr 是 int &const int cx = x; // cx 是 const intconst int &rx = x; // rx 是 const int &int *pp = &x; // pp 是 int *//下面是傳值的模板,由于傳入參數的值不影響原值,所以參數類型退化為原始類型f(x); // T 是 intf(cx); // T 是 intf(rx); // T 是 intf(rr); // T 是 intf(pp); // T 是 int*,指針比較特殊,直接使用//下面是傳引用模板, 如果輸入參數類型有引用,則去掉引用;如果沒有引用,則輸入參數類型就是 T 的類型func(x); // T 為 intfunc(cx); // T 為 const intfunc(rx); // T 為 const intfunc(rr); // T 為 intfunc(pp); // T 是 int*,指針比較特殊,直接使用//下面是通用引用模板,與引用模板規則一致function(x); // T 為 int&function(5); // T 為 int }上面代碼中可以將類型推導分成兩大類:其中類型不是引用也不是指針的模板為一類; 引用和指針模板為另一類。
對于第一類其推導時根據的原則是,函數參數傳值不影響原值,所以無論你實際傳入的參數是普通變量、常量還是引用,它最終都退化為不帶任何修修飾的原始類型。如上面的例子中,const int &類型傳進去后,退化為 int 型了。
第二類為模板類型為引用(包括左值引用和右值引用)或指針模板。這一類在類型推導時根據的原則是去除對等數量的引用符號,其它關鍵字照般。還是我們上面的例子,func(x)中 x 的類型為 int&,它與 T& 放在一起可以知道 T 為 int。另一個例子 function(x),其中 x 為 int& 它與 T&& 放在一起可知 T 為 int&。
根據推導原則,我們可以知道通用引用最終的結果是什么了,左值與通用引用放在一起推導出來的 T 仍為左值,而右值與通用引用放在一起推導出來的 T 仍然為右值。
三、move 的返回類型
實際上上面通過模板推導出的 T 與 move 的返回類型息息相關的,要講明白這一點我們先要把 move 的返回類型弄明白。下面我們就來討論一下 move 的返回類型:
typename remove_reference<T>::type&&move 的返回類型非常奇特,我們在開發時很少會這樣寫,它表示的是什么意思呢?
這就要提到 C++ 的另外一個知識點,即類型成員。你應該知道 C++ 的類成員有成員函數、成員變量、靜態成員三種類型,但從 C++11 之后又增加了一種成員稱為類型成員。類型成員與靜態成員一樣,它們都屬于類而不屬于對象,訪問它時也與訪問靜態成員一樣用::訪問。
了解了這點,我們再看 move 的返類型是不是也不難理解了呢?它表達的意思是返回 remove_reference 類的 type 類型成員。而該類是一個模板類,所以在它前面要加 typename 關鍵字。
remove_reference 看著很陌生,接下來我們再分析一下 remove_reference 類,看它又起什么作用吧。其實,通過它的名子你應該也能猜個大概了,就是通過模板去除引用。我們來看一下它的實現吧。
template <typename T> struct remove_reference {typedef T type; //定義T的類型別名為type };template <typename T> struct remove_reference<T &> //左值引用 {typedef T type; }template <typename T> struct remove_reference<T &&> //右值引用 {typedef T type; }上面的代碼就是 remove_reference 類的代碼,在 C++ 中 struct 與 class 基本是相同的,不同點是 class 默認成員是 private,而 struct 默認是 public,所以使用 struct 代碼會寫的更簡潔一些。
通過上面的代碼我們可以知道,經過 remove_reference 處理后,T 的引用被剔除了。假設前面我們通過 move 的類型自動推導得到 T 為 int&&,那么再次經過模板推導 remove_reference 的 type 成員,這樣就可以得出 type 的類型為 int 了。
remove_reference 利用模板的自動推導獲取到了實參去引用后的類型?,F在我們再回過來看 move 函數的時候是不是就一目了解了呢?之前無法理解的 5 行代碼現然變成了這樣:
int &&move(int &&&&t) {return static_cast<int &&>(t); } //或 int &&move(int &&&t) {return static_cast<int &&>(t); }經上面轉換后,我們看這個代碼就清晰多了,從中我們可以看到move實際上就是做了一個類型的強制轉換。如果你是左值引用就強制轉換成右值引用。
四、引用折疊
上面的代碼我們看起來是簡單了很多,但其參數 int& && 和 int && && 還是讓人覺得很別扭。因為 C++ 編譯器根本就不支持這兩種類型。咦!這是怎么回事兒呢?
到這里我們就要講到最后一個知識點引用折疊了。在C++中根本就不存 int& &&、int && && 這樣的語法,但在編譯器內部是能將它們識別出來的。換句話說,編譯器內部能識別這種格式,但它沒有給我們提供相應的接口(語法)。
實際上,當編譯器遇到這類形式的時候它會使用引用折疊技術,將它們變成我們熟悉的格式。其規則如下:
- int & & 折疊為 int&
- int & && 折疊為 int&
- int && & 折疊為 int&
- int && && 折疊為 int &&
總結一句話就是左值引用總是折疊為左值引用,右值引用總是折疊為右值引用。
經過這一系列的操作之后,對于一個具體的參數類型int & a,std::move 就變成了下面的樣子:
int &&move(int &t) {return static_cast<int &&>(t); }這一下我們就清楚它在做什么事兒了哈!
?
轉載于:http://avdancedu.com/a39d51f9/
?
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的Cpp / std::move 原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cpp / 右值、纯右值、将亡值
- 下一篇: Cpp / 通用引用、引用折叠与完美转发