I Like to Move It
Posted on May 19th, 2009 at 12:03 by fr3@K

喜愛 C++ 並關心 C++0x 標準的朋友一定會知道, C++0x 引進了許多新的元素. 不但對 Standard Library 有為數眾多的 addition 與 enhancement, 連 core language 都新增了不少東西. 而其中最重要的, 在我看來, 就是 Rvalue References. 即便不理會 C++0x 其他的新玩意 Rvalue References 依然是一個職業 C++ programmer 該要掌握的新 feature. 原因很簡單, 正確的運用 Rvalue References, 有可能帶來可觀的 performance gain.

The Stage

兩週前, 在 一篇文字 中提到了一個因為貪玩寫下的 object 字串化/格式化 utility. 並 預告 了將在另一篇文字討論, 在 C++0x 的標準下如何對這個 utility 進行優化. Well, here it is. 先回頭看看原來的 code:

    Listing 1
    C++:
    1. template <class T>
    2. string str(const T& x)
    3. {
    4.   return boost::lexical_cast<string>(x);
    5. }
    6.  
    7. template <class T>
    8. string operator%(string s, const T& x)
    9. {
    10.   return s += str(x);
    11. }

這個 utility 包含了兩個 function template. 使用方法如下:

    Listing 2
    C++:
    1. string output = str(123) % ' ' % "abc";

簡單的說, 使用時需先以 function template str 觸發這字串化 utility, 接下來以另一個 function template - operator% - 串接繼續要被字串化的的 object.

The Problem

你知道簡單如 Listing 2 的 use case 會觸發幾次 string 的建構 (與解構) 嗎? 假設每次 lexical_cast 的 invocation 只會觸發一次 string instantiation. 在 compiler 進行了 RVO 之後, 仍有五次 string instantiation.

其中三次是建構從 str/lexical_cast 傳回的 string object,1 另外兩次是 operator% 返回時的 copy construction. 前者可說是無中生有,2 是躲不掉的開銷. 而後者兩次的 construction 雖然一樣是閃不掉, 但在 C++0x 的 Rvalue References 加持下, 其 copy 的 overhead 是可以避開的.

The Move

C++0x 的 Rvalue References 可說是為了達成 move semantics 而誕生的產物. 這裡的 move, 是把一個 object 管理的 resource 轉移到另外一個 object 身上的動作. 而 move construction, 則是把一個 object 的 resource 讓出來, 以創建另一個 object:

    Listing 3
    C++:
    1. // a function that returns a temporary string.
    2. string unnamed_source();
    3.  
    4. void move_construction_demo()
    5. {
    6.   // move-construct a string using unnamed source.
    7.   string target(unnamed_source());
    8.  
    9.   string named_source(...);
    10.  
    11.   // move-construct another string using named source;
    12.   string another_target(move(named_source));
    13.  
    14.   // "named_source" is now destructible, but not usable.
    15.   // Any reference to it may result undefined behavior.
    16. }

請參考在之前的文字介紹過的一個字串 class 的 move constructor 以及 move function template 的 definition.

The Solution

有了 Rvalue References, 我們就可以把 Listing 1operator% template 修改成:

    Listing 4
    C++:
    1. template <class T>
    2. string operator%(string s, const T& x)
    3. {
    4.   return move(s += str(x));
    5. }

在這個例子中, 只要在回傳前對 s += str(x) 的結果 - an lvalue (reference to a string) - 呼叫 move, 就能把原本的 copy construction 變成 move construction. 當然也避開了 copy 的開銷.

那為什麼不需要對另外一個 function template - str - 一併做 move? 由於來源是 rvalue (lexical_cast 的傳回值), 而 C++0x 的 string class 也有 move constructor. 在這組合的配合下, move 會自動發生. 即便 string class 沒有 move ctor, 從 lexical_cast 傳回的 string instance 也會被一路 RVO 到 str 的 caller,3 其效果就跟被 move 了是十分接近的.

The Conclusion

C++0x 就要來了. 你能做的最好準備, 就是讓自己熟悉 Rvalue References. 它不是個 fancy feature, 而是能夠實實在在主宰程式的效能的 killer feature.

當 function 是以 return by value 方式傳回一個 named instance, 請記得: "We like to~~ MOVE IT!!!"

Full source code: move-it.cpp.

Compile using g++ 4.3.2: $ g++ -std=c++0x move-it.cpp

  1. 經過 RVO 之後, 已無法區分 strlexical_cast 傳回的 instance. 兩者融為一體. []
  2. 從沒有 string object 到有 string object. []
  3. 至於這樣的 RVO 會不會真的發生, 就要看 compiler 優化的功力了. []
del.icio.us:I Like to Move It digg:I Like to Move It spurl:I Like to Move It newsvine:I Like to Move It furl:I Like to Move It Y!:I Like to Move It 黑米共享書籤:I Like to Move It 推推王:I Like to Move It
Previous Post
« New Job Openings at Work «
Next Post
» My First JavaScript – Konami Code »

10 Comments »

Comment #6451

從語言層面支援的 move 終於快要能用了,真是讓人望穿秋水啊 XD

Comment by av — May 19, 2009 @ 20:48


Comment #6470

好個望穿秋水, 形容得很貼切. :)

Comment by fr3@K — May 20, 2009 @ 16:28


Comment #6502

看你網誌的介紹,覺得比較易了解RValue Reference了。可是我之前看其他的網頁中,有用到「兩個&」這個符號。如以下:
http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=310
那又是什麼呢?

另外,這以下的function
string operator%(string s, const T& x)
如果有用inline 的話,照理來講compiler應該會是做和macro 代入展開一樣,照理來講根本應該不會有return,也不會有copy ctor發生吧?

Comment by Miki — May 21, 2009 @ 14:59


Comment #6511

Hi Miki,

事實上我第一次知道到 Rvalue References 這玩意, 也是因為你提到的那篇 Danny Kalev 的文章. 可惜的是 該文的 sample code 有錯誤, 也讓當時剛開始嘗試了解 Rvalue References 的我遇到了挫折. 兩年前曾嘗試過留言與 email, 很遺憾, 那個錯誤依然不動如山.

回到你的問題. 在 (語意上的) return 時會不會發生 copy 與 inline 不 inline 沒有關係. 換個說法, function 的語意不該因為 inline 而改變. 撇開 move 不談, 原本在 return 時該有的 copy 會不會略去與 compiler optimization 有關, or more specifically, RVO. 這方面建議可以參考 Lippman 的 “Inside the C++ Object Model”, Chapter 2.3 裡的 “Return value initialization”, “Optimization at the user level” 以及 “Optimization at the compiler level” 等章節. 如果你手邊沒有的話, 網路上也能找到其解釋, 如 這裡. Just google for “RVO C++”.

副帶一提, 你提到了「兩個&」(&&) 這個符號. 其實若不是 code 中的 operator% template 的目的只是要串接繼續要被字串化的的 object, 因而總是可以享受 RVO, 否則這個 template 可能該寫成下列兩個 overload:

    template <class T>
    string operator%(string&& s, const T& x)
    {
      return move(s += str(x));
    }
    template <class T>
    string operator%(const string& s, const T& x)
    {
      string tmp(s);
      return move(tmp) % x;
    }
    

我曾在另外一篇文字嘗試以一個 sample string class 來解釋 && 的使用時機與方法, 提供你參考.

Comment by fr3@K — May 21, 2009 @ 20:17


Comment #6538

FYI. Here is a Rvalue Reference information from Microsoft Visual C++ Team.

http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

Comment by sam — May 22, 2009 @ 12:36


Comment #6550

And here is a Channel 9 video, which have some discuss C++0x new features. (http://tinyurl.com/r6l4m8)

Comment by sam — May 22, 2009 @ 18:49


Comment #6577

Thanks, sam.

Comment by fr3@K — May 23, 2009 @ 14:11


Comment #8633

前輩…冒昧請教一下

後兩次的 construction 應該可以改寫 operator% 如下, 就能省下來
string& operator%(string &s, const T& x) {…}

而無中生有的那三次的確似乎就非靠 move 不可了, 不知是否正確?

Comment by nono — August 3, 2009 @ 1:13


Comment #8643

Hi nono,

無論 const 與否, 以 string& 只能 bind 到 Lvalue (named instance). 而 str() 回傳的是 Rvalue, 因此你的提議不 work.

Comment by fr3@K — August 3, 2009 @ 11:19


Comment #8789

感激! 發現誤解的來源了…
我是用 VC2005 測試的, 而 VC 偷偷對 str(123) 做了 rvalue-to-lvalue 轉換
因此下面的代碼可在 VC 下編譯通過

template <class T>
string& operator%(string &s, const T& x) {
return s += string(x);
}

int main() {
string output = string("123") % "456";
}

而 g++(3.4.6)不允許, 所以的確不是個通用的好方法

    ed: minor correction in code snip – fr3@K.

Comment by nono — August 8, 2009 @ 21:51


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>