一直想試著把自己理解和學習到的右值引用相關的技術細節整理並分享出來,希望能夠對感興趣的朋友提供幫助。
右值引用是C++11標准中新增的一個特性。右值引用允許程序員可以忽略邏輯上不需要的拷貝;而且還可以用來支持實現完美轉發的函數。它們都是實現更高效、更健壯的庫。
先不展開具體右值引用定義。先說說move語義。右值引用是用來支持move語義的。move語義是指將一個同類型的對象A中的資源(可能是在堆上分配,也可能是一個文件句柄或者其他系統資源)搬移到另一個同類型的對象B中,解除對象A對該資源的所有權。這樣可以減少不必要的臨時對象的構造、拷貝以及析構等動作。比如我們經常使用的std::vector<T>,當兩個相同的std::vector類型賦值時,一般的步驟如下:
這就是我們C++11之前使用的拷貝語義,也就是常說的深拷貝。move語義與拷貝語義相對,類似於淺拷貝,但是資源的所有權發生了轉移。move語義的實現可以減少拷貝動作,大幅提高程序的性能。
而為了實現move語義的構造,就需要對應的語法來支持。原有的拷貝構造函數等不能夠滿足該需求。最典型的例子就是C++11廢棄的std::auto_ptr,其構造函數會產生不明確的擁有權關系,很容易滋生BUG。這也是很多人不喜歡std::auto_ptr的原因。C++11為此增加了相應的構造函數。
class Foo { public: Foo(Foo&& f) {} Foo& operator=(Foo&& f) { return *this; } };
這裡可以明顯看到兩個函數中的參數類型是Foo&&。這就是右值引用的基本語法。這樣做的目的是通過函數重載實現不同的功能處理。
C++11規定即可以在右值上使用move語義,也可以在左值上使用move語義。也就是說,可以把一個左值轉為右值引用,然後使用move語義。比如在C++的經典函數swap中:
template<class T> void swap(T& a, T& b) { T tmp(a); a = b; b = tmp; } X a, b; swap(a, b);
上面代碼中沒有右值,但是tmp變量只作用在本函數作用域中,只是用來承擔數據的轉移動作。C++11制定的上述規則在這裡反而可以得到非常好的適用。C++11為了達到這個規則,實現了std::move函數,這個函數的就是把傳入的參數轉換為一個右值引用並返回。也就是說在C++11下,swap的實現如下:
template<class T> void swap(T& a, T& b) { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); } X a, b; swap(a, b);
我們在實際使用中,也可以盡量的多使用std::move。只要求我們自定義的類型實現轉移構造函數。