1. 程式人生 > >[C++] 右值引用:移動語義與完美轉發

[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);}
繫結規則

  引入右值引用後,『引用』到『值』的繫結規則也得到擴充:

  1. 左值引用可以繫結到左值: int x; int &xr = x;
  2. 非常量左值引用不可以繫結到右值: int &r = 0;
  3. 常量左值引用可以繫結到左值和右值:int x; const int &cxr = x; const int &cr = 0;
  4. 右值引用可以繫結到右值:int &&r = 0;
  5. 右值引用不可以繫結到左值:int x; int &&xr = x;
  6. 常量右值引用沒有現實意義(畢竟右值引用的初衷在於移動語義,而移動就意味著『修改』)。

  其中,第五條規則『不適用於』函式模板的形參,例如下面的函式可以接受任意型別的引數,既可以是右值也可以是左值,還可以是常量或者非常量:

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++ 引用

右值引用用於獲取匿名變數的使用權,並延長匿名變數的生命週期,比如表示式的返回和函式的返回,都是由編譯器建立的一些變數,沒有右值引用的時候只能通過賦值給 中間變數的方法來保持住這個值,但是這也增加了一次構造的開銷。為了能夠保持這個值而且又不用去再次構造那麼就可以用右值引用。除