1. 程式人生 > >右值引用、移動語義

右值引用、移動語義

1.左值與右值

  • 左值的幾種定義
    a.可以取地址的,有名字的就是左值(但const常量不能做為左值
    b.左值則是有名稱的,能夠被操作的物件
    c.在記憶體中有獨立的記憶體空間,並且記憶體空間的內容是可變的,也就是通常所說的變數
  • 右值的幾種定義
    a.不能直接取地址,沒有名稱的,將立即被銷燬的物件
    b.右值是自動產生的,不被使用者直接操作的
int a; // a左值
int b = a + 1; // (a + 1)是右值,b是左值

2. 左值引用(&)

  • 用法:Type & 左值引用名 = 左值表示式;
  • 注意:宣告時必須初始化,初始化之後無法再改變
    (即無法將引用再繫結到另一個變數);對別名的一切操作都等價於對原來變數的操作。
  • 左值引用和指標都相當於是通過地址來訪問具體的值,因此可以修改
int & r = val + 1; //此句不合法,因為右值無法賦值給非常量左值引用
const int& r = val + 1//合法
//解釋:資料說c++中臨時變數預設const屬性,所以只能傳給const的引用。規定右值不能繫結到非 const 限定的左值引用。

3. 右值引用(&&)

  • C++11 新特性
  • 用法:Type && 右值引用名 = 右值表示式;
  • 在上面的程式碼中,我們無法建立 int &rb = a + 1 這樣的語法,因為a + 1 此時是作為一個右值來使用的,我們無法把一個右值賦值給一個左值引用。(也就是左值引用相當於把一個變數的地址賦給另一個變數,這兩個變數可以訪問同一個記憶體,右值僅僅是一個數,而非記憶體中的某塊地址,因此無法把右值複製給左值引用)。

4. 移動語義(std::move)

  • 解決的是各種情形下物件的資源所有權轉移的問題
  • 用法:int && rrval = std::move(val);
  • 用途:對於左值,如果我們明確放棄對其資源的所有權,則可以通過std::move()來將其轉為右值引用。當你不需要在使用一個變數的時候,可以直接通過該建構函式來實現把該變數的資料轉換到另一個變數中,省去呼叫預設的賦值構造或者拷貝建構函式帶來額外的開銷
  • 注意:在呼叫完std::move之後,不能再使用val,只能使用 rrval,這一點用於基本型別可能沒什麼直接影響,當應用到類函式的時候,用好std::move 可以減少建構函式數的次數
#include <iostream>
#include <utility>  //std::move標頭檔案
#include <vector>
#include <string>
 
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v; 
    // 使用 push_back(const T&) 過載,
    // 表示我們將帶來複制 str 的成本
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n"; 
    // 使用右值引用 push_back(T&&) 過載,
    // 表示不復制字串;而是 str 的內容被移動進 vector
    // 這個開銷比較低,但也意味著 str 現在可能為空。
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";
    std::cout << "The contents of the vector are \"" << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}

執行結果:看到move後str為空,不再可用

After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"

5.移動建構函式

/**
 *  拷貝建構函式呼叫時機: 
 *      1. 物件作為函式引數
 *      2. 物件作為函式返回值
 *      3. 用一個物件初始化另一個物件: 
 *          T t1;
 *          T t2(t1);
 *          T t3 = t1;  此處的 = 不是賦值運算子
 *  
 *  拷貝賦值運算子:
 *      T t1;
 *      T t2;
 *      t1 = t2; 
 *      除了 類名 物件 = 物件 外的 =  應該都是賦值運算子
 *  
 *  移動建構函式:
 *      用右值初始化物件。
 *      std::move(物件)將物件轉為右值
 *  
 *  移動賦值運算子
 *      T t1;
 *      t1 = std::move(T());
 */

#include <iostream>       // std::cout 
class A
{
public:
    int a;
 
    //一個引數的建構函式(也叫做轉換建構函式)
    A(int i):a(i)
        printf("construct is called! %d \n",i);
    ~A()
        printf("deconstruct is called! \n");
 
    //拷貝建構函式
    A(const A &v)
    {
        this->a = v.a;
        printf("copy construct is called %d\n", a);
    }
    
    //拷貝賦值運算子
    A& operator = (const A &v)
    {
        printf("= is called \n");
        if(this == &v)
            return *this;
        a = v.a;
        return *this;
    }
 
    //移動建構函式
    A(A &&v)
    {
        this->a = v.a;
        printf("move construct is called %d\n", a);
    }
 
    //移動賦值運算子
    A& operator = (A &&v)
    {
        printf("move = is called \n");
        if(this == &v)
            return *this;
        a = v.a;
        return *this;
    }
};
 
int main ()
{
    A a(1);    //呼叫建構函式
    A b(a);    //呼叫拷貝建構函式
    A c = a;   //呼叫拷貝建構函式
    
    A d(2);
    d = a;     //呼叫拷貝賦值運算子
    
    A e = std::move(A(3)); //呼叫移動建構函式
    A f(4);
    f = std::move(A(5));   //呼叫移動賦值運算子
    return 0;
}

參考

https://blog.csdn.net/lengyue_wuxin/article/details/78720085