C++11 右值引用&&、移動語義std::move、完美轉發std::forward

參考:https://blog.csdn.net/HR_Reborn/article/details/130363997

 

#pragma once
class Array {
public:
    Array() : size_(0), data_(nullptr){

    }
    Array(int size) : size_(size) {
        data_ = new int[size_];
    }

    // 複製構造函數 (深拷貝構造)
    Array(const Array& temp_array) {
        size_ = temp_array.size_;
        data_ = new int[size_];
        for (int i = 0; i < size_; i++) {
            data_[i] = temp_array.data_[i];
        }
    }

    // 賦值構造函數 (深拷貝賦值)
    Array& operator=(const Array& temp_array) {
        delete[] data_;

        size_ = temp_array.size_;
        data_ = new int[size_];
        for (int i = 0; i < size_; i++) {
            data_[i] = temp_array.data_[i];
        }
        //返回對象的引用,是爲了做鏈式處理: f1 = f2 = f3;
        return *this;
    }

    // 移動構造函數
    Array(Array&& temp_array) {
        cout << "移動構造函數\n";
        data_ = temp_array.data_;
        temp_array.data_ = nullptr;// 爲防止temp_array析構時delete data,提前置空其data_
    }

    // 移動賦值構造函數
    Array& operator=(Array&& temp_array) {
        cout << "移動賦值構造函數\n";
        data_ = temp_array.data_;
        temp_array.data_ = nullptr;// 爲防止temp_array析構時delete data,提前置空其data_
        return *this;
    }


    ~Array() {
        delete[] data_;
    }

public:
    int *data_;
    int size_;
};


// --> 測試 完美轉發
void B(int&& ref_r) {
    ref_r = 1;
}

// A、B的入參是右值引用
// 有名字的右值引用是左值,因此ref_r是左值
void A(int&& ref_r) {
    //B(ref_r);  // 錯誤,B的入參是右值引用,需要接右值,ref_r是左值,編譯失敗

    B(std::move(ref_r)); // ok,std::move把左值轉爲右值,編譯通過
    B(std::forward<int>(ref_r));  // ok,std::forward的T是int類型,屬於條件b,因此會把ref_r轉爲右值
}

void func1(int& i){
    cout << "參數是左值," << i << endl;
}
void func1(int&& i){
    cout << "參數是右值," << i << endl;
}

void func(int&& i){
    //注意:形參 i是左值
    func1(i);//這樣怎麼都是調用的 左值引用的 函數。爲解決,用完美轉發std::forward
}

// 1.如果模板中(包括類模板、函數模板)的參數爲 T&& 參數名,那麼函數既可以接受左值引用,也可以接受右值引用。
//2. std::forward<T>(參數), 用於轉發參數。若參數是左值,轉發後仍然是左值,若參數是右值,轉發後仍然是右值。
template<class T>
void func(T&& t){
    func1(std::forward<T>(t));
}

// <-- 測試 完美轉發

class TestRightValueRef
{
public:
    TestRightValueRef() = delete;
    ~TestRightValueRef() = delete;

    static void testAll(){
        //test02();
        test03();
    }
    static int foo(){ return 0; }
    static void test01(){

        /*
        左值,lvalue,就是賦值符號左邊的值,如a=5,a就是左值,但是準確的來說,左值是表達式(不一定是賦值表達式)後仍然存在的持久對象。
        右值,rvalue,右邊的值,是指表達式結束就不存在的臨時對象。
            純右值,prvalue,用於計算的或者用於初始化對象的的右值。例如 int a = 5; // 5是右值  注意 字符串 "abc" 這個不是右值,是指針
            ( 在介紹將亡值之前對左右和右值做個總結,有地址的變量就是左值,沒有地址的字面值、臨時值就是右值。)
            將亡值,xvalue, 是C++11爲了引入右值引用而提出的概念,與純右值的不同點在於,將亡值是即將被銷燬、卻能夠被移動的值。例如,函數中返回的對象。
        有地址的變量就是左值,沒有地址的字面值或臨時值就是右值。
        */

        /*
        左值引用:只能指向左值。不能指向右值的就是左值引用
        右值引用:只能指向右值,不能指向左值,符號是&&
        */

        int a = 5;// a 是左值, 5是右值
        int b = foo();// b 是左值, foo()是右值

        int& ra = a;        //左值引用
        int& rb = b;        //左值引用
        int&& rc = 5;        //右值引用
        int&& rd = foo();    //右值引用

        //int& r = 5;            //不能指向右值,編譯報錯
        //int&& r = a;            //不能指向左值,編譯報錯

        //但const 左值引用  都可以
        const int& rf1 = a;
        const int& rf2 = 3;
        const int& rf3 = foo();
        //爲什麼?因爲const 左值引用不會修改指向值,因此可以指向右值,這也是爲什麼要使用const &作爲函數參數的原因之一,如std::vector的push_back:
        //void push_back(const value_type& val);
        //如果沒有const,vec.push_back(5)這樣的代碼就無法編譯通過了。

        //左值引用默認只能指向左值,但是加了const的情況下可以指向右值,那麼右值引用有沒有類似的機制來指向左值呢?有的,就是std::move。
        //std::move 唯一的功能是把左值強制轉換爲右值,可以讓右值引用指向左值,等於一個強制類型轉換。
        int&& rg = std::move(a);
        
        //注意,被聲明出來的左、右值引用都是左值,因爲被聲明出的左右值引用是有地址的,也位於等號左邊。
        // 所以,ra,rb,rc,rd 都是左值


        /*
總結:

    從性能上講,左右值引用沒有區別,傳參使用左右值引用都可以避免拷貝。
    右值引用可以直接指向右值,也可以通過std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
    作爲函數形參時,右值引用更靈活。雖然const左值引用也可以做到左右值都接受,但它無法修改,有一定侷限性

    void f(const int& n) {
        n += 1; // 編譯失敗,const左值引用不能修改指向變量
    }
     
    void f2(int && n) {
        n += 1; // ok
    }
     
    int main() {
        f(5);
        f2(5);
    }
————————————————
版權聲明:本文爲CSDN博主「HR_Reborn」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/HR_Reborn/article/details/130363997
        */


    }


    //在實際場景中,右值引用和std::move被廣泛應用於在STL和自定義類中實現移動語義,避免拷貝,從而提升程序性能。
    static void test02(){
        // 例1:Array用法
        Array a(2);

        // 做一些操作
        //.....

        // 左值a,用std::move轉化爲右值
        Array b(std::move(a));//會調用移動構造函數
        //std::move(a) 後 a 不應再被使用,會報錯。因爲nullptr
        //b = a;    //error

        Array c;
        Array d;
        c = d;//調用賦值構造函數

        c = std::move(d);//調用移動賦值構造函數

    }

    static void test03(){
        //完美轉發
        /*
        完美轉發std::forward

        和std::move一樣,std::forward本質上是進行類型轉換,即左值右值之間的轉換,與move相比,forward更強大,move只能轉出來右值,forward都可以。

        std::forward<T>(u)有兩個參數:T與 u。 
        a. 當T爲左值引用類型時,u將被轉換爲T類型的左值; 
        b. 否則u將被轉換爲T類型右值。

        舉個例子,有main,A,B三個函數,調用關係爲:main->A->B。

        */
        int a = 5;
        A(std::move(a));

        cout << "-------------\n";
        func(a);
        func(5);
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章