之前拜讀過 yoco315 所翻譯的 Rvalue References 文章, 把原本相當複雜的概念寫得非常淺顯易懂, 讓我當時忍不住手癢實驗了一下.
由於這是一個全新的特性, 當時還沒有很完整的 coding standard 可參考, 因此越寫疑惑就越深, 最後就先把注意力轉移到其他特性, 如 lambda expression.
由於這是一個全新的特性, 當時還沒有很完整的 coding standard 可參考, 因此越寫疑惑就越深, 最後就先把注意力轉移到其他特性, 如 lambda expression.
今天的 OSDC.tw 很幸運地遇到 fr3@K 給了一場 C++11 的 talk, 讓我又再次燃起熱血, 便藉著指導新生的機會也來順便練習 class design.
首先要說的是, 使用 pimpl idiom 的 classes 較容易從 rvalue reference 和 swap 受益. 原因無它, handle classes 的成員變數只有一個指標, 無論是 move 或是 swap 都只要改變指標的值即可, 無須額外的 allocation 即可達成目的. 另外一個巨大的好處是可以讓實作與介面真正的分離, 這在一個大型的 C++ project 可以省下非常多的編譯時間. 未使用 pimpl idiom 的 classes 通常會比較麻煩一點, 因為很可能不是所有的 member variable 都被實作為 movable.
那麼要如何實作一個 movable 的 class? 最少必須要實作 move constructor 以及 overload move assignment operator. e.g.:
class BigInteger { public: BigInteger( BigInteger && that ); BigInteger & operator =( BigInteger && that ); private: class Private; std::unique_ptr< Private > p_; };Move constructor 的實作很簡單, 把別人的 member variable 全搶過來就對了:
BigInteger::BigInteger( BigInteger && that ): p_( move( that.p_ ) ) { }Move assignment operator 的邏輯也一樣, 反正 rvalue 之後就會蒸發, 你可以隨意惡搞它:
BigInteger & BigInteger::operator =( BigInteger && that ) { this->p_.swap( that.p_ ); return *this; }注意這和一般版本的 assignment operator 不同, 你不需要也不能做證同測試, 因為每個 rvalue 都是不一樣的, 而且 rvalue 也無法取址.
好的, 這就是所有我們所需要的; 但是不是所有的函式都應該加上 rvalue reference 版本的 overloading 呢? 基本上, 我摸索出來的大原則是, 只有當你需要偷別人的 resource 來建立一個新複本時才需要 rvalue reference. 舉例來說, 這樣子的 operator 就沒必要使用 rvalue reference:
bool operator ==( const BigInteger & a, const BigInteger & b );因為這個動作是 read-only, 並且沒有必要複製物件. 但當你需要回傳一個新的物件時, 你可以慎重考慮增加 rvalue 支援. 為什麼說要慎重考慮呢? 以 binary operators 為例:
BigInteger operator +( const BigInteger & a, const BigInteger & b ); BigInteger operator +( const BigInteger & a, BigInteger && b ); BigInteger operator +( BigInteger && a, const BigInteger & b ); BigInteger operator +( BigInteger && a, BigInteger && b );你必須要為四種不同的組合個別提供 overloading version. 第四個版本其實沒多大意義, 因為就算兩個 operand 都是 rvalue, 最後還是只能 move 其中一個 operand. 但沒有這個版本, 在兩端都是 rvalue 的使用條件下會出現岐義, 因為 compiler 不確定要把哪一端 bind 成 lvalue.
另外, 和 RVO (Return Value Optimization) 的 concept 不同, RVO 是 compiler 的一個最佳化技巧, 它是直接消除 copy constructor 的呼叫動作, 但 move semantic 的宗旨是廉價的 copy, 它還是需要呼叫 constructor, 只是這個 constructor 的成本很低. 當你需要 move semantic 時, 請記得使用 std::move, 否則 compiler 會視狀況呼叫 copy constructor:
T T::function() { // return T(); <- may calls T( const T & ) return std::move( T() ); // calls T( T && ) }
缺憾是目前 N2439 還沒有 compiler 實作出來, 不然就可以 move *this:
class BigInteger { public: BigInteger operator -() const &; BigInteger operator -() const &&; };第二個版本可以在自己就是 rvalue 的情況下直接把自己 move 出去, 其他的情況就乖乖得複製一份複本.
BigInteger::BigInteger( BigInteger && that ): p_(move(that.p_) ) {
回覆刪除}
噢對, 這樣也可以!
回覆刪除我看到 MSDN 上的另一個寫法是
*this = std::move( that );
好像也很省事, 只是 member 就不能在 initialize list 做 move 了
*this = move(that); // ???
回覆刪除Bad smell.
http://msdn.microsoft.com/en-us/library/dd293665.aspx
回覆刪除In the page end.
Bad smell confirmed.
回覆刪除其實好的作法是反過來的. 用 move ctor 去實現 move assignment op.
另外, 在 ctor 裏面用 member initialization list 也是大家都該知道的 best practice.