左值右值的一點總結

再次來寫左值右值相關的東西個人心裏是十分惴惴不安的,一來這些相關的概念十分很差理解,二來網上相關的文章實在太多了,多少人一看這類題目便大搖其頭,三來也怕說不清反而誤導了別人,反覆糾纏這些彷佛無關大雅的語言細節實在也有成爲 language lawyer 之嫌。但我仍是決定再總結一次,由於這是我一直以來學習新東西的一種方式,只有把學到的東西真正寫清楚說明白了纔是真的理解了,再者也但願本身的經驗總結能幫助到有一樣困惑的人。html

左值右值

咱們一直說左值右值,從 c++ 的術語角度來看,這其實並不十分準確,確切地說應該是左值表達式,右值表達式:表達式是有值的,值是有類型的,值是動態的,類型是靜態的,這是基本的概念。而咱們說的左值右值,是對值的一種分類,這兩個稱呼也是從 c 時代遺傳下來的:左值是指能出如今等號左邊的值,右值是指只能出如今等號右邊的值。簡單來講,左值就是咱們平時定義的變量,右值就是一些臨時變量。但到了 c++11,這個分類被細化擴充了,以下所示[N3690,3.10.1]:
c++

所以:算法

一個表達式要麼是一個 glvalue(generalized lvalue),要麼是一個 rvalue;一個 glvalue 要麼是一個 lvalue,要麼是一個 xvalue(expiring value);一個 rvalue 要麼是一個 xvalue,要麼是一個 prvalue (pure rvalue)。函數

乍一看好像狀況變得好複雜,其實不是,圖中所說 lvalue 與 c 時代的 lvalue 幾乎表達一個意思(不妨稱爲純左值),prvalue 與 c 時代的 rvalue 也幾乎表達一個意思,所謂純右值 (pure rvalue),只是多了一個 xvalue,一個介於純左傳與純右值之間的奇怪物種。本質上來講,xvalue 是 c 時代的 lvalue,它不是中間變量臨時變量之類的沒有名字的純右值,之因此再創造出這樣一個新的值類型是由於有些時候,咱們但願可以將一個純左值當成臨時變量(純右值)同樣來使用,這種被當成純右值來使用的左值就是 xvalue,提及來很繞,本質上 xvalue 就是一些從程序邏輯上看要 "過期" 的變量(expiring value),它的名字也正是取義自這裏。學習

xvalue 只能經過兩種方式來得到,這兩種方式都涉及到將一個左值賦給(轉化爲)一個右值引用[N3690,3.10.1]:指針

  1. 強制類型轉化爲右值引用,如 static_cast<T&&>(t); 該表達式獲得一個 xvalue。
  2. 返回類型爲右值引用的函數調用,如, T&& fun() { return t; };, 則調用 fun() 時, 返回一個 xvalue。

對於第 2 點,有一個與之相似的寫法值得你們注意:若是一個函數的返回類型是左值引用,那麼調用這個函數獲得的返回值將是一個 lvalue[N3690,3.10.1],之因此特別地說這個事,是由於若是一個函數的返回值不是引用類型,那調用這個函數獲得的結果將是一個臨時變量,是個右值,並且是純右值(prvalue),嗯,不要搞昏了。c++11

值與引用

這是另外兩個容易混爲一談的概念,引用在 c++ 裏是一個很特別的東西,就個人理解,確切地說引用應是一種類型,和 int, float 等相似,好比說 T& t = v;, 則 t 是一個變量,它的類型是引用,指向一個左值,所以也稱爲左值引用,因此 t 自己是一個左值,類型是一個左值引用。與此相對應,咱們也有右值引用: T&& t2 = fun();,一樣的 t2 是一個左值,但它的類型是右值引用。因此,咱們平時說的"引用"確切來講應該稱做"引用變量"才準確,與"整型變量","字符變量"相對(N3690, 8.5.3.1)。固然引用變量這個說法比較牽強(甚至有些政治不正確),畢竟它和通常變量相比實在太不類似了,好比它通常不佔內存,好比對它取地址,獲得的不是變量自己的地址,好比定義時必須初始化,且不能再次被賦值等等特殊之處,怎麼看都是異類。不少人傾向於把引用與指針並論來理解,它們其實也不大同樣,雖然實現上基本能夠認爲引用是一個由編譯器自動幫你解引用(deference)的指針。code

須要注意的是,左值引用變量只能用左值(lvalue)來初始化,右值引用變量只能用右值(xvalue 及 prvalue)來初始化,惟一的例外是 const 類型的左值引用,它也能用右值來初始化。值得注意的是,引用對臨時變量的生命週期是有影響的,如前面所說,臨時變量是右值,當一個臨時變量被一個引用(const 左值引用或右值引用)指向時,它的生命週期會被延長[N3690,12.2.5]。htm

關於右值引用,還有一個很容易讓人迷惑的語義須要說一說,寫法上定義一個右值引用變量的語法如右所示:some_type&& rv_ref = some_rvalue;,但這裏要求 somt_type 必須是一個具體的完整的類型,而不能是模板參數,auto,或 decltype 等須要推導的類型,若是 T 是一個須要推導的類型,則 T&& u_t_ref 稱爲 universal reference 或 forwarding reference,根據 reference collapsing 原則及右值引用推導原則,u_t_ref 最後既多是左值引用也多是右值引用,具體能夠參看這裏blog

move 與 forward

接下來是不少人特別關心的 std::move()std::forward(),對這倆偏偏想總結的卻很少,總的來講,以個人淺見,std::move() 的主要做用是將一個左值轉爲 xvalue, 它的實現,本質上就是一個 static_cast<>。而 std::forward() 則是用來配合 forwarding reference 實現完美轉發,它主要的做用是將一個類型爲引用(左值引用或右值引用)的左值,轉化爲它的類型所對應的值類型,這個說法實在是太沒法理解太不知所云了哈哈,因此我放棄用本身的話來解釋了,請參看 cppreference 上的例子

move 語義

move 語義是一個你們必定要注意,接受,掌握,並理解好的東西。過去使用 c++ 98/03 咱們常說 rule of three,便是:若是一個類定義了析構函數,拷貝構造函數,賦值構造函數之一,那麼這三個函數都應該要明肯定義,目的是爲了確保該類的拷貝語義被正確地處理。

到了 c++11,這個 rule of three 得改爲 rule of five。咱們知道,一個類若是定義了拷貝構造函數和賦值構造函數,則咱們稱它爲 copyable 的類。同理,若是一個類定義了 move constructor 和 move assignment operator,那麼咱們稱它爲 movable 的類。從使用上來講,右值引用是一個很 tricky 的東西,它的正確使用場合應該只有兩個:一個是爲自定義類型實現 move 語義,一個是配合 forwarding reference 來實現完美轉發。當你考慮寫一個以右值引用做爲參數類型的通常函數時,每每這是錯誤的開始,正確的做法是爲相應的類型定義 move 語義,具備 move 語義的類型在做爲參數傳遞時,要麼直接傳值(sink parameter),要麼傳 const 左值引用(read only),根本不須要右值引用這種 tricky 的東西。

所以,可以在適當時候爲自定義類型實現 move 語義是一個基本素質,就正如之前處理 copy 語義同樣(會不會將一個類繼承自 boost::noncopyable 也是基本素質)。STL 中全部的容器算法都妥善定義了對適用類型 move 語義的要求,如只適用於 copyable,或只適用於 movable 等,容器自己更都是 movable 的。通常來講,move 是一個更輕量的操做,對容器其實更友好(內部 copy 能夠改成 move,效率更高),好比 vector<>,之前要求其所保存類型必須 copyable,如今 c++11 之後,只 movable(且 noexcept)的類型也被容許了(固然此時用戶就不能調用那些須要 copy 的操做了)。因此,明肯定義好自定義類型的 move 語義,意義是很大的。

相關文章
相關標籤/搜索