1. 程式人生 > >c++ 右值引用,move關鍵字

c++ 右值引用,move關鍵字

賦值函數 .cpp 一次 -s 編譯器 一份 簡單 som this

c++ move關鍵字

move的由來:在 c++11 以前存在一個有趣的現象:T& 指向 lvalue (左傳引用), const T& 既可以指向 lvalue 也可以指向 rvalue。但卻沒有一種引用類型,可以限制為只指向 rvalue。

c++11 中的 move() 是這樣一個函數,它接受一個參數,然後返回一個該參數對應的右值引用.

就這麽簡單!你甚至可以暫時想像它的原型是這樣的(當然是錯的)

T&& move(T& val);

&&的由來:在函數體中,程序員無法分辨傳進來的參數到底是不是 rvalue,我們缺少一個 rvalue 的標記。為了解決這個問題,c++11 中引入了一個新的引用類型: some_type_t &&,這種引用指向的變量是個 rvalue。

由於&和&&是屬於不同的類型,所以用於各種函數(構造函數,賦值函數)的重載

holder(holder& other)
holder(holder&& other)

上面是2個重載函數

holder& operator=(holder& other)
holder& operator=(holder&& other)

上面是2個重載函數

具體看下面的例子:假設我們有一個類,它包含了一些資源。我們的願望是:當調用拷貝構造函數或者賦值語句時,我們不想再new一個Resource的對象,因為要new一個Resource對象,即浪費空間,有浪費時間。

解決辦法:利用右值引用。

右值,本質上是一個臨時的內存空間,使用過後,系統馬上就會釋放掉它,有了右值引用後,就可以延長這個臨時空間的生命周期,相當於有了左值的效果,所以可以使用這個臨時空間了。回到上面提出的問題,我們不想再new一個Resource的對象,這時我們就可以利用右值引用,去引用一個臨時的並且馬上要被釋放的空間。

為了調用&&的拷貝構造函數,必須使用std::move,轉化成右值引用.

holder h2(std::move(get_holer()))

但是,h1 = get_holer(),即使不使用std::move,也會調用&&的賦值函數

h1 = get_holer()

完整代碼:

#include <iostream>
using namespace std;

class Resource{};

class holder{
public:
  //構造函數                                                       
  holder(){res = new Resource();}
  //析構函數                                                       
  ~holder(){
    //res不為NULL,就釋放res                                     
    if (res)delete res;
  }
  //拷貝構造函數                                                   
  holder(const holder& other){
    cout << "holder&" << endl;
    res = new Resource(*other.res);
  }
  holder(holder& other){
    cout << "holder&1" << endl;
    res = new Resource(*other.res);
  }

  //右值                                                           
  holder(holder&& other){
    cout << "holder&&" << endl;
    res = other.res;
    other.res = nullptr;
  }

  //賦值                                                           
  holder& operator=(const holder& other){
    cout << "operator" << endl;
    delete res;
    res = new Resource(*other.res);
    return *this;
  }
  holder& operator=(holder& other){
    cout << "operator1" << endl;
    delete res;
    res = new Resource(*other.res);
    return *this;
  }

  //右值                                                           
  holder& operator=(holder&& other){
    cout << "operator &&" << endl;
    std::swap(res, other.res);
    return *this;
  }

private:
  Resource* res;
};

holder get_holer(){
  holder h;
  return h;
}

int main(void){
  holder h1,h11;
  holder h2(std::move(get_holer()));//調用holder(holder&& other)
  holder h3(get_holer());//編譯器自動優化了,沒有調用拷貝構造函數  
  holder h4(h11);

  h1 = h2;//調用operator(holder&);                                 
  h1 = get_holer();//調用operator(holder&&);                        

}

上面的例子有個需要註意的地方,就是下面這行代碼

holder h3(get_holer());//編譯器自動優化了,沒有調用拷貝構造函數  

這行代碼乍一看,應該調用拷貝構造函數。但是實際用GDB,斷點調試時,發現並沒有調用拷貝構造函數。

個人的猜測:如果編譯器不優化,在函數get_holer()的return一行處就應該有一次拷貝,調用拷貝構造函數,把h拷貝一份返回給調用測,然後由於holder h3(get_holer())的寫法,又要調用一次拷貝構造函數,把get_holer()返回值拷貝給h3。這樣一來就多了2次不必要的拷貝,所以編譯器自動優化,把這2次拷貝構造函數的調用都省略掉了,直接讓h3 = 在get_holer()創建的對象

65        holder h2(std::move(get_holer()));                       
(gdb) n  
holder&& 
66        holder h3(get_holer());//編譯器自動優化了,沒有調用拷貝構造函數 
(gdb) s  
get_holer () at rvalue_move.cpp:59                                 
59        holder h;//調用一次構造函數,在下面的9行可以看到                                                
(gdb) s                                                            
holder::holder (this=0x7fffffffe180) at rvalue_move.cpp:9 
9         holder(){res = new Resource();}  //調用構造函數,創建h對象
(gdb) s 
get_holer () at rvalue_move.cpp:60 
60        return h; //返回h對象
(gdb) p h  //查看h對象裏面res的內存地址
$12 = {res = 0x603070} 
(gdb) p &h //查看h的內存地址
$13 = (holder *) 0x7fffffffe180  
(gdb) n       
61      } 
(gdb) n  
main () at rvalue_move.cpp:67  
(gdb) p h3 //查看h3對象裏面res的內存地址,發現h3的res的內存地址和在函數get_holer()裏創建的h對象的res的內存地址相同
$14 = {res = 0x603070}                                             
(gdb) p &h3 //查看h3的內存地址後,發現h3的內存地址和在函數get_holer()裏創建的h對象的內存地址相同
$15 = (holder *) 0x7fffffffe180

c++ 右值引用,move關鍵字