C++ 右值引用和對象移動

1. 左值和右值

  • 左值:能對錶達式取地址,例如(有名字的)變量、類對象等;
  • 右值:不能對錶達式取地址,例如非引用的函數返回值、表達式計算的臨時變量、字面常量等;

一個左值表達式表達的是對象的身份,一個右值表達式表達的是對象的值,左值可以當做右值使用,但右值不能當做左值使用。——《C++ Primer》

2. 左值引用和右值引用

  • 聲明:

    • 左值引用: &
    • 右值引用:&&
    int i=2;    //i是左值
    int &li=i;  //聲明li爲左值引用
    int &&ri=2; //聲明ri爲右值引用, 2是右值(字面常量)
    
  • 左值引用只能綁定左值,右值引用只能綁定右值

  • const左值引用既可以綁定左值,也可以綁定右值

    int i=2;
    const int &a=i;//const左值引用綁定左值
    const int &b=3;//const左值引用綁定右值
    
  • 有名字的變量一定是左值,即使該變量是一個右值引用;

    int &&a=2; //2是右值,a是右值引用,同時a也是一個右值引用變量,是一個左值
    int &&b=a;//錯誤,右值變量不能綁定左值
    

    拷貝構造函數中第一個參數爲const左值引用,就是因爲const左值引用既可以綁定左值也可以綁定右值

  • std::move()實現左值向右值的轉換

    int a=2;
    int &&b=std::move(a);//將左值a轉換爲右值,便可以與右值引用綁定
    

3.移動構造函數和移動賦值函數

  • 移動構造函數的第一個參數是該類類型的右值引用

  • 移動構造函數不分配任何新內存,而是直接接管內存,接管後,將傳入對象中的指針置爲nullptr,這樣在該臨時對象被銷燬時,不會釋放被接管的內存;

  • 編寫移動構造函數和移動賦值函數

    class String
    { 
    public: 
    	 String(const char *str = NULL); // 普通構造函數 
    	 String(const String &other); // 拷貝構造函數 
    	 ~ String(void); // 析構函數 
    	 String& operator=(const String &other); // 拷貝賦值函數 
    	 
    	 String(String &&other) noexcept;//移動構造函數
    	 String& operator=(String&& other) noexcept;//移動賦值函數
    	 
    private: 
    	 char *m_data; // 用於保存字符串 
    };
    
    • 移動構造函數:
    String::String(String &&other) noexcept: m_data(other.m_data){
    	other.m_data=nullptr;
    }
    
    • 移動賦值函數:
    String::String& operator=(String &&other)noexcept{
    	//避免自賦值
    	if(this==&other)
    		return *this;
    	delete [] m_data;
    	m_data=other.m_data;
    	other.m_data=nullptr;
    	return *this; 
    }
    
    • 移動構造函數和移動賦值函數的使用:
    int main(){
    	vector<String> vec;
    	vec.push_back(String("hello"));//因爲String("hello")是右值,所以優先調用移動構造函數
    	String tmp("hello");
    	vec.push_back(tmp);//因爲tmp是左值,所以優先調用拷貝構造函數
    	vec.push_back(std::move(tmp));//std::move(tmp)是右值,所以優先調用移動構造函數
    	//如果String沒有定義移動構造函數,傳入std::move(tmp)也不會報錯,因爲const左值引用可以綁定右值
    	//使用移動構造函數之後,tmp的m_data指針已經變成空指針,但是tmp對象仍然存在,直到其作用域結束才刪除
    	String tmp2("hello");
    	String tmp3,tmp4;
    	tmp3=tmp2;//因爲tmp2是左值,因此調用拷貝賦值函數
    	tmp4=std::move(tmp2);//因爲std::move(tmp2)是右值,因此調用移動賦值函數
    	//如果String沒有定義移動構造函數,傳入std::move(tmp)也不會報錯,因爲const左值引用可以綁定右值
    	//調用移動賦值函數後,tmp2的m_data指針已經變成空指針,但是tmp2對象仍然存在,直到其作用域結束才刪除
    }
    

4. 左值引用成員函數 和 右值引用成員函數

  • 類似const成員函數,成員函數加上const關鍵字表明調用該成員函數的對象是const對象,成員函數加上引用限定符來表明調用該成員函數的對象是左值還是右值;
  • &代表左值限定,&&代表右值限定;
  • 引用限定符必須跟在const關鍵字之後(如果有const關鍵字的話);
  • 如果一個成員函數有引用限定符,則其重載的所有版本都必須有引用限定符;
    class Foo{
    public:
        //重載的所有版本都必須有引用限定符,或者都沒有
    	Foo sorted() &&;       //用於右值Foo對象
    	Foo sorted() const &;  //用於任何類型的Foo對象
    private:
    	vector<int> data;
    };
    Foo Foo::sorted()&&{   //對象爲右值,可以原址排序
    	sort(data.begin(), data.end());
    	return *this;
    }
    Foo Foo::sorted() const &{ //對象爲const,不能原址排序,對象爲左值,不能原址排序
    	Foo ret(*this);
    	sort(ret.data.begin(), ret.data.end());
    	return ret;
    }
    
  • 當對一個右值執行sorted的時候,可以安全的直接對data成員進行排序。對象是一個右值,意味着沒有其他用戶,因此可以改變對象。當一個const對象或者左值執行sorted的時候,不能改變對象,因此需要排序前拷貝data;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章