[C++] 右值引用:移動語義與完美轉發
C++11 引入的新特性中,除了併發記憶體模型和相關設施,這些高帥富之外,最引人入勝且接地氣的特性就要屬『右值引用』了(rvalue reference)。加入右值引用的動機在於效率:減少不必要的資源拷貝。考慮下面的程式:
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)。
有了右值的語法支援,為了實現移動語義,需要相應類以右值為引數過載傳統的拷貝建構函式和賦值操作符,畢竟哪些資源可以移動、哪些只能拷貝只有類的實現者才知道。對於移動語義的拷貝『構造』,一般流程是將源物件的資源繫結到目的物件,然後解除源物件對資源的繫結;對於賦值操作,一般流程是,首先銷燬目的物件所持有的資源,然後改變資源的繫結。另外,當然,與傳統的構造和賦值相似,還要考慮到構造的異常安全和自賦值情況。作為演示:
classString{public:String(constString&rhs){...}String(String&&rhs){
s_ = rhs.s_;
rhs.s_ = NULL;}String&operator=(constString&rhs){...}String&operator=(String&&rhs){if(this!=&rhs){delete[] s_;
s_ = rhs.s_;
rhs.s_ = NULL;}return *this;}private:char*s_;};
值得注意的是,一個繫結到右值的右值引用是『左值』,因為它是有名字的。考慮:
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 轉換為右值:
class D :public B {
D(D &&rhs): B(std::move(rhs)){}};
std::move 的一種可能的實現:
template<typename T>typename remove_reference<T>::type&&
move(T &&t){returnstatic_cast<remove_reference<T>::type&&>(t);}
繫結規則
引入右值引用後,『引用』到『值』的繫結規則也得到擴充:
- 左值引用可以繫結到左值: int x; int &xr = x;
- 非常量左值引用不可以繫結到右值: int &r = 0;
- 常量左值引用可以繫結到左值和右值:int x; const int &cxr = x; const int &cr = 0;
- 右值引用可以繫結到右值:int &&r = 0;
- 右值引用不可以繫結到左值:int x; int &&xr = x;
- 常量右值引用沒有現實意義(畢竟右值引用的初衷在於移動語義,而移動就意味著『修改』)。
其中,第五條規則『不適用於』函式模板的形參,例如下面的函式可以接受任意型別的引數,既可以是右值也可以是左值,還可以是常量或者非常量:
template<typename T>void foo(T &&t);int x;constint xx;
foo(x);//~ OK
foo(xx);//~ OK
foo(10);//~ OK
T&& 形參可以接受左值,是 C++11 針對這種特殊情況做的規則修訂,目的是為了實現『完美轉發』(perfect forwarding)。
完美轉發
C++11 之前,一直存在著引數『轉發』的問題,即不能方便地實現完美轉發。轉發的目的在於傳遞『引用引數』的附加屬性,比如 cv 屬性(const/volatile)和左右值屬性。為了刻畫這個問題,我們以左右值屬性的傳遞為例(cv 屬性也存在相似的問題),參考下面的類定義:
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_;};
為了支援移動語義,就需要過載建構函式,由於建構函式有兩個引數,還需要考慮到右值引用和左值引用的組合形式:
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 中,通過基於右值引用的函式模板解決了這個問題,本質上是通過對實參型別的推演,按照實際情況,由編譯器完成自動的『過載』。
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 就可以簡單地實現為:
template<typename T>
T&& forward(T &&t){returnstatic_cast<T&&>(t);}
總結
C++11 中引入很多特性,大多讓人眼前一亮:靠,這就是我一直想要的啊!很多特性瀏覽一遍就清晰了,但右值引用相關的,尤其是完美轉發相對來說比較繞,難以理順。右值引用有兩個應用,最基本的動機是移動語義,同時又給完美轉發的支援帶來契機。
相關推薦
[C++] 右值引用:移動語義與完美轉發
C++11 引入的新特性中,除了併發記憶體模型和相關設施,這些高帥富之外,最引人入勝且接地氣的特性就要屬『右值引用』了(rvalue reference)。加入右值引用的動機在於效率:減少不必要的資源拷貝。考慮下面的程式: std::vector<string>
《深入理解C++11》筆記–右值引用:移動語義和完美轉發
上一篇:《深入理解C++11》筆記–建構函式 這篇文章介紹的了第三章中右值引用相關的內容。在介紹該內容之前,會對一些相關問題進行解釋,便於理解後面的內容。 並且,提前說明,許多編譯器會多拷貝構造和移動構造進行優化省略,這樣就看不到拷貝構造和移動構造的過程,需
C++11:右值引用、移動語意與完美轉發
在C++11之前我們很少聽說左值、右值這個叫法,自從C++11支援了右值引用之後,大多數人會像我一樣疑惑:啥是右值? 準確的來說: 左值:擁有可辨識的記憶體地址的識別符號便是一個左值。 右值:非左值。 左值引用:左值識別符號的一個別名,簡稱引用
C++右值引用和移動語義淺說
1、神馬叫右值和右值引用C++中所有的值分兩種,一種叫左值(可以取地址,有名字的),一種叫右值(不可以取地址,沒有名字的)。常見的如 int a = b+c ;表示式中a 可以取地址為左值,(b+c
《C++0x漫談》系列之:右值引用(或“move語意與完美轉發”)(下)
《C++0x漫談》系列之:右值引用 或“move語意與完美轉發”(下) By 劉未鵬(pongba) 《C++0x漫談》系列導言 這個系列其實早就想寫了,斷斷續續關注C++0x也大約有兩年餘了,其間看著各個重要proposals一路review過來:rvalue-r
C++11:深入理解右值引用,move語義和完美轉發
深入右值引用,move語義和完美轉發 轉載請註明:http://blog.csdn.net/booirror/article/details/45057689 乍看起來,move語義使得你可以用廉價的move賦值替代昂貴的copy賦值,完美轉發使得你可以將傳來的任意
深入右值引用,move語義和完美轉發
轉載:http://blog.csdn.net/booirror/article/details/45057689 乍看起來,move語義使得你可以用廉價的move賦值替代昂貴的copy賦值,完美轉發使得你可以將傳來的任意引數轉發給 其他函式,而右值引用使得move語
C++的雜七雜八:我家的返回值才不可能這麼傲嬌(右值引用和移動語義)
大凡程式語言,都會有“函式”這個概念。而對於外部而言,一個函式最重要的部分就是它的返回值了。 說這裡,返回值其實應該是一個很簡單的話題。當需要通過函式傳遞一個值出去的時候,使用返回值不是理所當然的嘛,比如說,像下面這樣: int add(int a, int b)
C++11特性--右值引用,移動語義,強制移動move()
1.右值引用 *右值:不能對其應用地址運算子的值。 *將右值關聯到右值引用導致該右值被儲存到特定的位置,且可以獲取該位置的地址 *右值包括字面常量(C風格字串除外,它表示地址),諸如X+Y等表示式以及返回值得函式(條件是該函式返回的不是引用) *引入右值引用的主要目的之一是實行移動語義 E
右值引用、移動語義
1.左值與右值 左值的幾種定義 a.可以取地址的,有名字的就是左值(但const常量不能做為左值) b.左值則是有名稱的,能夠被操作的物件 c.在記憶體中有獨立的記憶體空間,並且記憶體空間的內容是可變的,也就是通常所說的變數 右值的幾種定義 a.不能直接取地址,沒有名稱的,
C++11新特性:移動語義和右值引用
右值引用 傳統的C++引用(左值引用)使得識別符號關聯到左值。左值是一個表示資料的表示式(如變數名或解除引用的指標),程式可以獲得其地址。 C++11新增了右值引用。右值引用,顧名思義,可以關聯到右值,即——可以出現在賦值表示式的右邊,但不能對其應用地址運算
C++ 右值引用與移動操作
銷毀 帶來 臨時對象 類型 左值引用 都是 獲得 留下 c++11 右值引用和移動操作是C++11提出的新概念,通過這些操作,可以降低拷貝操作帶來的消耗。先來簡單介紹一下左值和右值。 左值一般指的是一個對象,或者說是一個持久的值,例如賦值的返回值、下標操作、解引用以及前置遞
c++11:物件移動 & 右值引用 & 移動建構函式
一、概述 c++ 11 新標準中最主要的特徵是可以移動而非拷貝物件的能力。很多情況下,物件拷貝後就會立即被銷燬。 在這些情況下,移動而非拷貝物件會大幅度提升效能。 在舊 C++ 標準中,沒有直接的方法移動物件。因此,即使不必要拷貝物件的情況下,我們也不得不拷貝。如果物件本身要求
深入淺出C++11(3) -- 右值引用和move語義
右值引用 什麼是lvalue, 什麼是rvalue? lvalue: 具有儲存性質的物件,即lvalue物件,是指要實際佔用記憶體空間、有記憶體地址的那些實體物件,例如:變數(variables)、函式、函式指標等。 rvalue:相比較於lvalue就是所謂的沒有儲存性質
c++右值引用以及使用
什麽 函數的參數 .html 顯式 pan 但是 cout 表達式 信息 前幾天看了一篇文章《4行代碼看看右值引用》 覺得寫得不錯,但是覺得右值引用的內容還有很多可以去挖掘學習,所以總結了一下,希望能對右值引用有一個更加深層次的認識 一、幾個基本概念 1.1左值和右值 左值
c++ 右值引用,move關鍵字
賦值函數 .cpp 一次 -s 編譯器 一份 簡單 som this c++ move關鍵字 move的由來:在 c++11 以前存在一個有趣的現象:T& 指向 lvalue (左傳引用), const T& 既可以指向 lvalue 也可以指向 rval
【轉載】c++右值引用以及使用
轉自:https://www.cnblogs.com/likaiming/p/9045642.html 前幾天看了一篇文章《4行程式碼看看右值引用》 覺得寫得不錯,但是覺得右值引用的內容還有很多可以去挖掘學習,所以總結了一下,希望能對右值引用有一個更加深層次的認識 一、幾個基本概念 1.
【強文翻譯】c++右值引用詳解
原文連結譯註:這篇是我讀過有關右值引用的文章中最通俗易懂的一篇,易懂的同時,其內容也非常全面,所以就翻譯了一下以便加深理解。有翻譯不準的地方請留言指出。INTRODUCTION右值引用是C++11標準中引入的新特性,由於右值引用所解決的問題並不是很直觀,所以很難在一開始就很好
右值、移動語義和完美轉發
1. lvalue / rvalue An lvalue is an expression that refers to a memory location and allows us to take the address of that memory
C++ 右值引用
右值引用用於獲取匿名變數的使用權,並延長匿名變數的生命週期,比如表示式的返回和函式的返回,都是由編譯器建立的一些變數,沒有右值引用的時候只能通過賦值給 中間變數的方法來保持住這個值,但是這也增加了一次構造的開銷。為了能夠保持這個值而且又不用去再次構造那麼就可以用右值引用。除