亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++右值引用,移動語義與完美轉發得方法

發布時間:2022-03-24 11:05:25 來源:億速云 閱讀:148 作者:iii 欄目:開發技術

本篇內容主要講解“C++右值引用,移動語義與完美轉發得方法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“C++右值引用,移動語義與完美轉發得方法”吧!

    C++——左值與右值、右值引用、移動語義與完美轉發

    在C++或者C語言中,一個表達式(可以是字面量、變量、對象、函數的返回值等)根據其使用場景不同,分為左值表達式和右值表達式。

    一、左值和右值的定義

    1.左值的英文為locator value,簡寫為lvalue,可意為存儲在內存中、有明確存儲地址(可尋址)的數據

    2.右值的英文為read value,簡寫為rvalue,指的是那些可以提供數據值的數據(不一定可尋址,例如存儲與寄存器中的數據)

    二、如何判斷一個表達式是左值還是右值(大多數場景)

    1.可位于賦值號(=)左側的表達式就是左值;反之,只能位于賦值號右側的表達式就是右值。例如:

    int a = 5;

    其中,變量a就是一個左值,而字面量5就是一個右值。

    注:C++中的左值可以當作右值使用,反之則不行,如

    int b = 10; //b是一個左值
    a = b; //a、b都是左值,只不過b可以當作右值使用
    10 = b; //錯誤,10是一個右值,不能當作左值使用

    2.有名稱的、可以獲取到存儲地址的表達式即為左值;反之則為右值

    以上面定義的變量a、b為例,a和b是變量名,則通過&a和&b可以獲得他們的存儲地址,因此a和b都是左值;反之,字面量5、10,它們既沒有名稱,也無法獲取其存儲地址(字面量通常存儲在寄存器中,或者和代碼存儲在一起),因此5、10都是右值

    三、C++右值引用

    C++ 98/03標準中就有引用,但這里的引用只能操作左值,無法對右值添加引用,故被稱為左值引用,例如:

    int num = 10;
    int &b = num; //正確
    int &c = 10; //錯誤

    注意,雖然C++ 98/03標準不支持為右值建立非常量左值引用,但允許使用常量左值引用操作右值。即,常量左值引用既可以操作左值,也可以操作右值,例如:

    int num = 10;
    const int &b = num; //正確
    const int &c = 10; //正確

    為什么需要右值引用?

    右值往往是沒有名稱的,因此要使用它只能借助引用的方式。這就產生一個問題,實際開發中我們可能需要對右值進行修改(如實現移動語義時),而是用常量左值引用的方式是無法做到這一點的。

    為此,C++11新標準引入了另一種引用方式,成為右值引用,用&&表示

    需要注意的是,和聲明左值引用一樣,右值引用也必須立即進行初始化操作,且只能使用右值進行初始化,比如:

    int num = 10;
    int &&a = num; //錯誤,右值引用不能初始化為左值
    int &&a = 10; //正確

    和常量左值引用不同的是,右值引用還可以對右值進行修改。例如:

    int &&a = 10;
    a = 100; //正確

    另外,C++語法上是支持定義常量右值引用的,例如:

    const int&& a = 10;

    但這種定義出來的右值引用并無實際用處。一方面,右值引用主要用于移動語義和完美轉發,其中前者需要有修改右值的權限;其次,常量右值引用的作用就是引用一個不可修改的右值,而這項工作常量左值引用就可以完成

    四、std::move()與移動語義

    C++11標準中,借助右值引用可以為指定類添加移動構造函數,這樣當使用該類的右值對象(可以理解為臨時對象)初始化同類對象時,編譯器會優先選擇移動構造函數。

    注:移動構造函數的調用時機是:用同類的右值對象初始化新對象。那么當用此類的左值對象(有名稱,能獲取其存儲地址的實例對象)初始化同類對象時,如何調用移動構造函數呢?C++11給出的解決方案就是調用std::move()函數。

    雖然move是移動的意思,但是該函數并不移動任何數據,它的功能是將某個左值強制轉化為右值,常用于實現移動語義

    用法示例:

    #include<iostream>
    #include<utility>
    using namespace std;
    class movedemo
    {
    public:
        movedemo():num(new int(0)) {
            cout << "construct!" << endl;
        }
        //copy constructor
        movedemo(const movedemo &d):num(new int(*d.num)) {
            cout << "copy constrct!" << endl;
        }
        //move constructor
        movedemo(movedemo &&d):num(d.num) {
            d.num = NULL;
            cout << "move construct!" << endl;
        }
    private:
        int *num;
    };
    int main() 
    {
        movedemo demo;
        cout << "demo2:\n";
        movedemo demo2 = demo;
        cout << "demo3:\n";
        movedemo demo3 = std::move(demo);//執行完之后demo.num會置為空
        return 0;
    }

    程序運行結果:

    construct!demo2:copy constrct!demo3:move construct!

    通過觀察程序的輸出結果,以及對比 demo2 和 demo3 初始化操作不難得知,demo 對象作為左值,直接用于初始化 demo2 對象,其底層調用的是拷貝構造函數;而通過調用 move() 函數可以得到 demo 對象的右值形式,用其初始化 demo3 對象,編譯器會優先調用移動構造函數。

    五、 完美轉發

    什么是完美轉發?

    它指的是函數模板可以將自己的參數“完美”地轉發給內部調用的其他函數。所謂完美,即不僅能準確地轉發參數的值,還能保證被轉發參數的左、右值屬性不變

    舉個栗子:

    template<typename T>void function(T t) {	otherdef(t);}

    如上所示,function()函數模板中調用了otherdef()函數。在此基礎上,完美轉發指的是:如果function()函數接收到的參數t為左值,那么該函數傳遞給otherdef()的參數t也是左值;反之如果function()函數接收到的參數t為右值,那么傳遞給otherdef()函數的參數t也必須為右值。

    顯然,上面這個例子中,function()函數模板并沒有實現完美轉發。一方面,參數t為非引用類型,這意味著在調用function()函數時,實參將值傳遞給形參的過程就需要額外進行一次拷貝操作;另一方面,無論調用function()函數模板時傳遞給參數t的是左值還是右值,對于函數內部的參數t來說,它有自己的名稱,也可以獲取它的存儲地址,因此它永遠都是左值,即傳遞給otherdef()函數的參數t永遠都是左值。總之,無論從哪個角度看,function()函數的定義都不“完美”。

    為什么需要完美轉發?

    C++11新標準中引入了右值引用和移動語義,因此很多場景中是否實現完美轉發,直接決定了該參數的傳遞過程使用的是拷貝語義(調用拷貝構造函數)還是移動語義(調用移動構造函數)

    如何實現完美轉發?

    C++98/03標準:由于沒有右值引用,只能通過重載函數模板(可通過常量左值引用傳遞右值)的方式實現轉發,且這種方式存在弊端,如:此實現方式只適用于模板函數僅有少量參數的情況,否則就需要編寫大量的重載函數模板,造成代碼的冗余。

    為了方便用戶更快速地實現完美轉發,C++11標準中允許在函數模板中使用右值引用來實現完美轉發

    C++11標準中規定,通常情況下右值引用形式的參數只能接收右值,不能接收左值。但對于函數模板中使用右值引用語法定義的參數來說,它不再遵守這一規定,既可以接收右值,也可以接收左值(此時的右值引用又被稱為“萬能引用”)

    仍以function()函數為例,在C++11標準中實現完美轉發,只需編寫如下一個模板函數即可:

    template<typename T>void function(T &&t) {otherdef(t);}

    此模板函數的參數t既可以接收左值,也可以接收右值。但僅僅使用右值引用作為函數模板的參數是遠遠不夠的,還有一個問題需要解決,即如果調用function()函數時為其傳遞一個左值引用或右值引用的實參,如下所示:

    int n = 10;int &num = n;function(num); //T為int &int &&num2 = 11;function(num2); //T為int &&

    其中由function(num)實例化的函數底層就變成了function(int& &&t),而由function(num2)實例化的函數底層則變成了function(int&& &&t)。C++98是不支持這種語法的,而C++11為了更好地實現完美轉發,專門為其指定了新的類型匹配規則,又稱為引用折疊規則(假設A表示實際傳遞參數的類型):

    • 當實參為左值或左值引用(A&)時,函數模板中T&&將轉變為A&(A& && = A&);

    • 當實參為右值或右值引用(A&&)時,函數模板中T&&將轉變為A&&(A&& && = A&&);

    上述規則的含義是:在實現完美轉發時,只要函數模板的參數類型為T&&,則C++可以自行準確地判定實際傳入的實參是左值還是右值

    通過將函數模板的形參類型設置為T&&,我們可以很好地解決接收左、右值的問題。但除此之外,還需要解決一個問題,即無論傳入的形參是左值還是右值,對于函數模板內部來說,形參既有名稱又能尋址,因此它都是左值。那么如何才能將函數模板接收到的形參連同其左、右值屬性,一起傳遞給被調用的函數呢?

    答案就是使用C++11提供的模板函數forward(),注意其和move的區別是forward要通過顯式模板實參來使用

    用法示例:

    #include <iostream>
    #include <utility>using namespace std;//重載被調用函數,查看完美轉發的效果
    template<typename T>
    void print(T &t)
    {    
    cout << "lvalue" << endl;
    }
    template<typename T>
    void print(T &&t) 
    {    cout << "rvalue" << endl;
    }
    template<typename T>
    void TestForward(T &&v) 
    {    
    print(v);    
    print(std::forward<T>(v));    
    print(std::move(v));
    }
    int main() 
    {    
    TestForward(1);  
    cout << endl;    
    int x = 1;    
    TestForward(x);    
    cout << endl;    
    TestForward(std::forward<int>(x));    
    return 0;
    }

    程序執行結果為:

    lvaluervaluervaluelvaluelvaluervaluelvaluervaluervalue

    到此,相信大家對“C++右值引用,移動語義與完美轉發得方法”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    c++
    AI

    紫云| 金沙县| 手游| 长顺县| 晋中市| 左云县| 嘉定区| 从化市| 宁南县| 江陵县| 吉安县| 大连市| 玉屏| 德格县| 同江市| 静乐县| 镇安县| 边坝县| 怀远县| 宜章县| 睢宁县| 重庆市| 措美县| 嘉兴市| 东明县| 呼图壁县| 枣阳市| 怀宁县| 文水县| 陈巴尔虎旗| 上高县| 稷山县| 昌吉市| 新竹市| 贺兰县| 南宫市| 肥乡县| 儋州市| 锡林郭勒盟| 霞浦县| 错那县|