C++ 0x 之左值與右值、右值引用、移動語意、傳導模板

來源:互聯網
上載者:User

文 / 李博(光宇廣貞)

    左值與右值

       左值與右值的概念要追溯到 C 語言,由 C++ 語言繼承了上來。C++ 03 3.10/1 如是說:“Every expression is either an lvalue or an rvalue.”左值與右值是指運算式的屬性,而非對像的屬性。

       左值具名,對應指定記憶體域,可訪問;右值不具名,不對應記憶體域,不可訪問。臨時對像是右值。左值可處於等號左邊,右值只能放在等號右邊。區分運算式的左右值屬性有一個簡便方法:若可對錶達式用 & 符取址,則為左值,否則為右值。

       注意區分 ++x 與 x++。前者是左值運算式,後者是右值運算式。前者修改自身值,並返回自身;後者先建立一個臨時對像,並用 x 的值賦之,後將修改 x 的值,最後返回臨時對像。

       函數的傳回值一般情況下是右值,C++ 03 5.2.2/10 如是說:“A function call is an lvalue if and only if the result type is a reference.”比如有 vector<int> v 對像,則 v[0] 即為左值,因為 vector 容器的 [] 算符重載函數的傳回值為引用。

       左值與右值均可以聲明為 const 和 non-const。

    拼接字串的問題

       上面提到函數傳回值一般為右值,也即臨時對像。對於內建類型(built-in type)來說,臨時對像還是可忍的。但對於容器對像來說就是極大的浪費了。舉一個 C++ 98/03 標準下最通俗的例子,拼接字串

圖一

       圖一第 18 行,短短一句,背後動作極其複雜。我要把 string 對像和常量字串交替拼接起來,問題重點在於如何重載 + 算符。有如下幾點需要考慮:

  1. 過程中分別出現 string 對像與常量字串的加法、string 對像與 string 對像的加法。因此需要重載多種 + 算符函數。(加法自左至右)
  2. 比如 string 對像與常量字串的加法,返回的將是一個新產生的 string 對像,因此必須返回這個對像的複本,是臨時對像,是右值。又由於加法是連續運算的,下一個加法的重載函數為了接收這一右值,參數表只得寫成傳值的形式,也即將此臨時對像再複製一次,才可傳到函數體內操作。總的來說,就是臨時對像由前一個函數體轉到另一個函數體,需要深度複製兩次。
  3. 由第二點可知,僅僅由一個加號過渡到另一個加號,就要產生兩個曇花一現、轉瞬即逝的臨時對像複本。若每個字串都很長,對像都很大,拼接個數又特別多,這要產生多少垃圾?為何不能把前一個函數返回時產生的臨時對像不用複製,直接拿給下一個函數用呢?也就是從前一個函數“移動”到後一個函數體中。
  4. 對於第三點,C++ 98/03 不允許這麼做。因為語義上不支援。由於缺乏“移動語意”,前一個函數產生的臨時對像將在函數體退出時析構,外部要想獲得只能使用其複本,本體已經不存在了。

    右值引用和移動語意

       針對上述拼接字串的問題,若說,函數返回時產生的臨時對像需要複製出去還情有可原——畢竟人家的範圍到頭兒了,本體的確不能傳遞到外部,只能由複本代勞(這是 C++ 與 C# 最大的不同之一);不過話又說回來,複本都複製出來了,為何傳遞到下一個函數體內還需要再複製一次呢?C++ 98/03 說得是理直氣壯:

       “因為我規定了,右值不但不能取址,連引用都不能取!誰讓丫傳的是臨時對像,是右值,傳參只能傳值!”

       話說得多氣人呐!憑什麼連引用都不能取?傳值就意味著深度複製。C++ 標準委員會發現了這一問題,決定在 C++ 0x 新標準中補充“右值引用”和“移動語意”。

       移動語意:將對方掏空,實體吸收給我自己。見《測試 VS 2010 對 C++ 0x 標準的謹慎支援》。

       舉一個臨時對像由一個函數傳往另一個函數的例子以說明問題。由例子可見,Sck 函數使用右值引用重載版本,接收 Fck 函數返回的臨時對像。而在 Fck 函數返回時,完成了一次 Sb 對像的複製。

圖二

      關於右值引用和移動語意的更多例子,請參見微軟 VC 官方部落格:《Rvalue Reference》。

    右值引用重載函數幾點

  1. 移動構造重載函數和移動賦值算符(assignment operators:=、^=、+=,etc.)重載函數絕不會隱式聲明,必須自己定義。
  2. 預設建構函式會被使用者自己顯式定義的建構函式壓制,包括使用者自訂複製建構函式和移動建構函式。因故若使用者已自訂複製和移動建構函式,且需要無參建構函式時,也需要自己定義。
  3. 隱式複製建構函式會被使用者自己顯式定義的複製建構函式覆蓋,而不是自訂的移動建構函式。
  4. 隱式複製賦值重載函數會被使用者自己顯式定義的複製賦值重載函數覆蓋,而不是自訂的移動賦值重載函數。

       總之一句話,一個類定義完了,程式員嘛也不管,預設建構函式、預設複製建構函式、預設複製賦值函數,編譯器都會自動產生。而移動語意的建構函式和賦值函數,則必須由程式員自己顯式定義方可使用。

    操作右值對像實現移動語意

       操作右值對像實現移動語意,須使用 std::move () 方法。無論是對類對像,還是對類對像的成員變數。使用 move 方法需要引用 <utility> 標頭檔。詳見下例:

圖三

    外圍函數向內建函式準確傳參的問題

       見如下代碼塊:

void Outer ( params ) { Inner ( params ); }

       由 Outer 函數接收參數後,要準確無誤地傳遞給 Inner 函數。所謂的準確無誤包括 params 的左、右值屬性和 const / non-const 屬性。此也即“參數傳導語義”。實現這一語義的目的是 Inner 函數的類型檢查資訊可以與 Outer 外部互連,因此由 Outer 到 Inner 之間的參數傳導不能對參數屬性有任何的改變。

       在 C++ 98/03 標準下,我們可以使用左值引用標識參數類型: T& params;但若我往裡傳常量呢?常量是右值,傳不進去。好,那改成 const T& params 好了,這下左右值都可以傳了;但若我要在函數體內修改 params 的值呢?……

       《C++ 0x 之 Lambda:賢妻與嬌娃,你娶誰當老婆?聽 FP 如何點化 C++》裡說:C++ 是萬能的。別以為 C++ 沒轍了,我可以重載啊!一種版本我滿足不了你的所有要求,我重載出滿足你要求的所有版本的函數就好了唄!

       嗯……C++ 果真賢惠!好,我一個參數表有 64 個參數,你把所有版本都重載去吧!估計得有 2^64 個這麼多……

    傳導模板:forward<>

       話說 C++ 0x 之前的 C++ 在這方面表現得實在是太糙了,簡直沒法兒看……我們所期待的完美解決方案是只用一個模板即可處理所有情況,而重載函數再能用也不能這麼用。好在 C++ 0x 的 <utility> 標頭檔中有 forward 模板:

       template < typename T > void Outer ( T&& t )
       {
              Inner ( std::forward<T> ( t ) );
       }

       不錯,寫這麼一個就解決了。首先 Outer 函數參數表使用 T&& 類型接收參數。推導過程如下:

  • 若參數 t 為 Type& 型即左值引用,則 T&& 推導為 Type& &&,歸化為 Type&,為左值引用。
  • 若參數 t 為 Type&& 型即右值引用,則 T&& 推導為 Type&& &&,歸化為 Type&&,為右值引用。
  • 若參數 t 為 const Type&(&&) 型,即常左(右)值引用,則 T&& 推導為 const Type&(&&) &&,歸化為 const Type&(&&)。
  • 若參數 t 為實值型別,則 T&& 為右值引用,待傳值型參數為右值。

       一句話,T&& 模板類型可以保留參數資訊

       Outer 使用 T&& 是解釋清楚了,那 forward<> 是如何保證由 Outer 到 Inner 的平穩過渡呢?若要知 std::move () 和 std::forward () 是如何?的,請參見:《C++ 0x 之移動語意和傳導模板如何?》。

    題外話:C++的歧義算符

       參見:《C++ 的歧義算符

       所屬分類:C++

       參考:

        遞迴餘七

        人和猴子分配椰子問題

        取球的機率演算法

        男人們基本不會作詩

        C語言之#define用法

        .NET中數組的隱秘特性

        如何列印 RichTextBox

        如何列印 DataGridView

        ListView Control 對 Column 進行排序的方法

        檔案關聯的操作

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.