轉載至: http://www.dutor.net/index.php/2013/11/rvalue-reference-move-semantics-and-perfect-forwarding/ php
C++11 引入的新特性中,除了併發內存模型和相關設施,這些高帥富以外,最引人入勝且接地氣的特性就要屬『右值引用』了(rvalue reference)。加入右值引用的動機在於效率:減小沒必要要的資源拷貝。考慮下面的程序:安全
1 2 |
std::vector<string> v; v.push_back("string"); |
向 vector 中添加一個元素,這個動做須要前後調用 string::string(const char*), string::string(const string&), string::~string() 三個函數,涉及兩次內存拷貝:第一次使用字面常量 「string」 構造出一個臨時對象,第二次使用該臨時對象構造出 vector 中的一個新元素,『最後臨時對象會發生析構』。併發
上面程序操做的問題癥結在於,臨時對象的構造和析構帶來了沒必要要的資源拷貝。若是有一種機制,能夠在語法層面識別出臨時對象,在使用臨時對象構造新對象(拷貝構造)的時候,將臨時對象所持有的資源『轉移』到新的對象中,就能消除這種沒必要要的拷貝。這種語法機制就是『右值引用』,相對地,傳統的引用被稱爲『左值引用』。左值引用使用 ‘&’ 標識(好比 string&),右值引用使用 ‘&&’ 標識(好比 string&&)。順帶提一下什麼是左值(lvalue)什麼是(rvalue):能夠取地址的具名對象是左值;沒法取值的對象是右值,包括匿名的臨時對象和全部字面值(literal value)。 有了右值的語法支持,爲了實現移動語義,須要相應類以右值爲參數重載傳統的拷貝構造函數和賦值操做符,畢竟哪些資源能夠移動、哪些只能拷貝只有類的實現者才知道。對於移動語義的拷貝『構造』,通常流程是將源對象的資源綁定到目的對象,而後解除源對象對資源的綁定;對於賦值操做,通常流程是,首先銷燬目的對象所持有的資源,而後改變資源的綁定。另外,固然,與傳統的構造和賦值類似,還要考慮到構造的異常安全和自賦值狀況。做爲演示:函數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class String { public: String(const String &rhs) { ... } String(String &&rhs) { s_ = rhs.s_; rhs.s_ = NULL; } String& operator=(const String &rhs) { ... } String& operator=(String &&rhs) { if (this != &rhs) { delete [] s_; s_ = rhs.s_; rhs.s_ = NULL; } return *this; } private: char *s_; }; |
值得注意的是,一個綁定到右值的右值引用是『左值』,由於它是有名字的。考慮:post
1 2 3 4 5 6 7 8 9 10 11 |
class B { public: B(const B&) {} B(B&&) {} }; class D : public B { D(const D &rhs) : B(rhs) {} D(D &&rhs) : B(rhs) {} }; D getD(); D d(getD()); |
上面程序中,B::B(B&&) 不會被調用。爲此,C++11 中引入 std::move(T&& t) 模板函數,它 t 轉換爲右值:this
1 2 3 |
class D : public B { D(D &&rhs) : B(std::move(rhs)) {} }; |
std::move 的一種可能的實現:spa
1 2 3 4 5 |
template <typename T> typename remove_reference<T>::type&& move(T &&t) { return static_cast<remove_reference<T>::type&&>(t); } |
引入右值引用後,『引用』到『值』的綁定規則也獲得擴充:.net
其中,第五條規則『不適用於』函數模板的形參,例以下面的函數能夠接受任意類型的參數,既能夠是右值也能夠是左值,還能夠是常量或者很是量:code
1 2 3 4 5 6 7 |
template <typename T> void foo(T &&t); int x; const int xx; foo(x); //~ OK foo(xx); //~ OK foo(10); //~ OK |
T&& 形參能夠接受左值,是 C++11 針對這種特殊狀況作的規則修訂,目的是爲了實現『完美轉發』(perfect forwarding)。對象
C++11 以前,一直存在着參數『轉發』的問題,即不能方便地實現完美轉發。轉發的目的在於傳遞『引用參數』的附加屬性,好比 cv 屬性(const/volatile)和左右值屬性。爲了刻畫這個問題,咱們以左右值屬性的傳遞爲例(cv 屬性也存在類似的問題),參考下面的類定義:
1 2 3 4 5 6 7 8 |
class X
{ public: X(const std::string &s, const std::vector<int> &v) : s_(s), v_(v) {} private: std::string s_; std::vector<int> v_; }; |
爲了支持移動語義,就須要重載構造函數,因爲構造函數有兩個參數,還須要考慮到右值引用和左值引用的組合形式:
1 2 3 4 5 6 7 8 9 10 11 |
class X
{ public: X(const std::string &s, const std::vector<int> &v) : s_(s), v_(v) {} X(std::string &&s, const std::vector<int> &v) : s_(std::move(s)), v_(v) {} X(const std::string &s, std::vector<int> &&v) : s_(s), v_(std::move(v)) {} X(std::string &&s, std::vector<int> &&v) : s_(std::move(s)), v_(std::move(v)) {} private: std::string s_; std::vector<int> v_; }; |
若是構造函數有 n 個參數,就須要 2^n 個重載! C++11 中,經過基於右值引用的函數模板解決了這個問題,本質上是經過對實參類型的推演,按照實際狀況,由編譯器完成自動的『重載』。
1 2 3 4 5 6 7 8 9 |
class X
{ public: template <typename T1, typename T2> X(T1 &&s, T2 &&v) : s_(std::forward<T1>(s)), v_(std::forward<T2>(v)) {} private: std::string s_; std::vector<int> v_; }; |
在介紹這種轉發以前,先須要知道右值引用形參的函數模板的實參推演規則,即引用摺疊(reference collapsing)。BTW. C++11 以前,不容許綁定到引用的引用類型(reference to reference)。 設 T 爲模板的類型參數,A 爲實參的基本類型,則有:
T | 形參 | 摺疊後的T | 摺疊後實參類型 |
---|---|---|---|
A& | T& | A | A& |
A& | T&& | A& | A& |
A&& | T& | A& | A& |
A&& | T&& | A | A&& |
能夠看到,當函數的形參聲明爲 T&& 時,當且僅當實參爲右值或者右值引用,摺疊後的的實參類型纔是右值引用,不然爲左值引用。經過這個摺疊規則,就能夠實現左右值引用屬性的轉發。std::forward 就能夠簡單地實現爲:
1 2 3 4 5 |
template <typename T> T&& forward(T &&t) { return static_cast<T&&>(t); } |
C++11 中引入不少特性,大多讓人眼前一亮:靠,這就是我一直想要的啊!不少特性瀏覽一遍就清晰了,但右值引用相關的,尤爲是完美轉發相對來講比較繞,難以理順。右值引用有兩個應用,最基本的動機是移動語義,同時又給完美轉發的支持帶來契機。