最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++左值引用、右值引用應(yīng)用場景分析

 更新時間:2026年03月20日 15:51:51   作者:白藏crab  
本文給大家介紹C++左值引用、右值引用應(yīng)用場景分析,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧

一、右值引用的意義

在 C++ 的編程世界中,對象的拷貝與資源管理一直是性能優(yōu)化的核心痛點。在 C++11 標(biāo)準(zhǔn)之前,我們編寫的代碼中隱藏著大量臨時對象的深拷貝開銷:比如函數(shù)返回一個非引用類型的對象、容器插入臨時對象時,都會觸發(fā)拷貝構(gòu)造函數(shù),完成堆內(nèi)存的重新分配與數(shù)據(jù)復(fù)制,而臨時對象在表達式結(jié)束后就會被銷毀,這就造成了完全無意義的內(nèi)存操作與性能浪費。

舉個最簡單的例子:std::string str = "hello" + " world"; ,表達式會生成一個臨時的 string 對象,再通過拷貝構(gòu)造把數(shù)據(jù)復(fù)制給 str,隨后臨時對象被銷毀。如果字符串很長,這個深拷貝的開銷是完全可以避免的。

而 C++11 引入的右值引用,正是為了解決這個問題。它不僅重新定義了 C++ 的資源管理哲學(xué),讓我們可以對即將銷毀的臨時對象的資源進行轉(zhuǎn)移而非拷貝,還支撐了泛型編程中的完美轉(zhuǎn)發(fā),成為現(xiàn)代 C++ 不可或缺的核心特性。

二、基礎(chǔ):理解左值與右值

1. 左值(Lvalue,Locator Value)

左值的核心定義是:可以通過&取地址運算符獲取其內(nèi)存地址、擁有持久生命周期的表達式。

簡單來說,左值就是一個 “有名字、有固定內(nèi)存地址” 的對象,你可以找到它的存儲位置,除了 const 修飾的左值外,都可以修改其內(nèi)容。左值既可以放在賦值號的左邊,也可以放在賦值號的右邊(作為右值使用)。

常見的左值場景:

  1. 普通變量、const 修飾的變量(const 左值不可修改,但依然可以取地址,屬于左值)
  2. 對指針解引用的結(jié)果*ptr
  3. 數(shù)組的元素arr[index]
  4. 結(jié)構(gòu)體 / 類的非靜態(tài)成員變量
  5. 返回左值引用的函數(shù)調(diào)用
  6. 前綴自增 / 自減表達式++i--i
int main()
{
    int i = 0;
    i = 1;                  // i是變量,可修改左值,能放在賦值號左側(cè)
    const int ci = 5;
    // ci = 6;             // 報錯:const左值不可修改,但&ci可獲取地址,仍是左值
    int* pi = &i;
    *pi = 1;                // *pi對指針解引用,左值
    int arr[5] = {0};
    arr[0] = 1;             // 數(shù)組元素,左值
    struct { int m_a; } st, *pst = &st;
    st.m_a = 1;             // 結(jié)構(gòu)體成員,左值
    pst->m_a = 2;
    return 0;
}

2. 右值(Rvalue,Read Value)

C++11 之后,右值被細分為純右值(prvalue,Pure Rvalue)將亡值(xvalue,eXpiring Value) 兩類,二者共同構(gòu)成了右值。

右值的核心定義是:無法通過&取地址、生命周期短暫的臨時表達式結(jié)果,它代表的是一個值,而非值所在的存儲位置。右值只能放在賦值號的右側(cè),不能放在左側(cè)。

2.1 純右值(prvalue)

純右值是 C++98 標(biāo)準(zhǔn)中傳統(tǒng)意義上的右值,核心是 “字面量、臨時計算結(jié)果”,沒有持久的存儲地址。

常見的純右值場景:

  1. 除字符串字面量外的字面常量,如42'a'、true(字符串字面量是 const char 數(shù)組類型,屬于左值)
  2. 算術(shù)、邏輯、關(guān)系運算符的求值結(jié)果,如1+2、a>10、a&&b
  3. 非引用類型返回值的函數(shù)調(diào)用結(jié)果
  4. 后置自增 / 自減表達式i++、i--
  5. lambda 表達式

2.2 將亡值(xvalue)

將亡值是 C++11 為了配合右值引用新增的概念,核心是 “資源即將被轉(zhuǎn)移、生命周期即將結(jié)束的對象”,它是連接左值和右值的橋梁。

將亡值雖然可以通過特殊方式獲取地址,但它的核心使命是完成資源轉(zhuǎn)移,使用后就會被銷毀,因此歸屬于右值范疇。

常見的將亡值場景:
  1. std::move()的返回結(jié)果
  2. 返回右值引用類型的函數(shù)調(diào)用結(jié)果
  3. 類型轉(zhuǎn)換為右值引用的表達式結(jié)果
int getNum()
{
    return 100;
}
int main()
{
    int i = 0, c = 0;
    i = 42;                  // 字面值42,純右值
    c = 'a';                 // 字面值'a',純右值
    i = 1 + 2;               // 算術(shù)表達式結(jié)果,純右值
    i = (c != 'a');          // 邏輯表達式結(jié)果,純右值
    i = getNum();            // 非引用返回值,純右值
    int&& rri = std::move(i);// std::move(i)的結(jié)果,將亡值,右值
    return 0;
}

3. 左值與右值的核心區(qū)別

核心特性左值右值(純右值 + 將亡值)
內(nèi)存地址可通過&獲取穩(wěn)定的內(nèi)存地址無法直接通過&獲取地址
生命周期持久,隨變量 / 對象的作用域存在臨時,表達式結(jié)束后即銷毀(除非被引用綁定)
可修改性非 const 左值可修改,const 左值不可修改不可直接修改
賦值位置可放在賦值號左側(cè)、右側(cè)只能放在賦值號右側(cè)
引用綁定可被左值引用、const 左值引用綁定只能被 const 左值引用、右值引用綁定

三、右值引用的定義與語法規(guī)則

1. 左值引用的回顧與局限

在 C++11 之前,我們使用的引用都是左值引用,用&符號聲明,本質(zhì)是給變量起一個 “別名”,底層通過指針實現(xiàn),必須在定義時初始化,且初始化后無法重新綁定到其他對象。

左值引用的核心局限:非 const 左值引用只能綁定到左值,無法綁定到右值。只有 const 左值引用是個例外,它可以同時綁定左值和右值,但 const 修飾決定了我們無法通過它修改綁定的對象。

int main()
{
    int a = 10;
    int& ra = a;             // 正確:非const左值引用綁定左值
    // int& rb = 10;         // 報錯:非const左值引用無法綁定右值
    const int& rca = a;      // 正確:const左值引用綁定左值
    const int& rcb = 10;     // 正確:const左值引用綁定右值
    // rcb = 20;             // 報錯:const引用無法修改綁定對象
    return 0;
}

這個局限帶來了兩個核心問題:

  1. 無法對臨時的右值對象進行修改和資源復(fù)用,只能通過拷貝使用
  2. 泛型編程中,無法區(qū)分傳入的參數(shù)是左值還是右值,無法實現(xiàn)精準(zhǔn)的參數(shù)轉(zhuǎn)發(fā)

而右值引用的出現(xiàn),完美解決了這兩個問題。

2. 右值引用的語法與本質(zhì)

右值引用是 C++11 新增的引用類型,用&&符號聲明,核心是專門綁定到右值(臨時對象、將亡值)的引用。

基礎(chǔ)語法:

類型&& 引用名 = 右值表達式;

右值引用的核心規(guī)則:

  1. 必須在定義時初始化,且初始化后無法重新綁定到其他對象
  2. 只能直接綁定到右值,無法直接綁定到左值
  3. 綁定到右值后,會延長該臨時右值的生命周期,生命周期與右值引用變量一致
  4. 可以通過右值引用修改綁定的右值對象
  5. 右值引用變量本身是左值(有名字、可通過&取地址),這是初學(xué)者最容易踩的坑
#include <iostream>
using namespace std;
int getNum()
{
    return 100;
}
void func(int&& rri)
{
    rri = 0;
}
int main()
{
    // 1. 右值引用必須綁定右值
    int&& ri0 = 42;          // 正確:綁定字面量純右值
    int&& ri1 = 1 + 2;       // 正確:綁定表達式結(jié)果純右值
    int&& ri2 = getNum();    // 正確:綁定函數(shù)返回值純右值
    // int&& ri3 = ri0;      // 報錯:ri0是右值引用變量,本身是左值,無法綁定
    // 2. 可通過右值引用修改綁定的對象
    ri0 = 100;
    cout << "ri0 = " << ri0 << endl; // 輸出:ri0 = 100
    // 3. 可獲取右值引用的地址(證明它本身是左值)
    int* pi = &ri0;
    *pi = 200;
    cout << "*pi = " << *pi << ", ri0 = " << ri0 << endl; // 輸出:*pi = 200, ri0 = 200
    // 4. 右值引用作為函數(shù)參數(shù),接收右值
    func(1 + 2);
    return 0;
}

3. std::move ()做了什么?

std::move()實現(xiàn)了資源的移動?這是錯誤的。

std::move()的本質(zhì):不做任何資源移動、不生成任何機器碼,只是無條件地將一個左值強制轉(zhuǎn)換為右值引用類型(將亡值),僅此而已。

它的唯一作用,就是讓左值可以被右值引用綁定,從而為后續(xù)的資源轉(zhuǎn)移提供可能。真正的資源轉(zhuǎn)移,是在類的移動構(gòu)造函數(shù)、移動賦值運算符中完成的,std::move()只是打開了 “資源轉(zhuǎn)移” 的入口。

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string str = "Hello World!";
    cout << "move前:str = " << str << endl;
    // std::move將str左值轉(zhuǎn)為右值,觸發(fā)string的移動構(gòu)造函數(shù)
    string tmp = std::move(str);
    cout << "move后:str = " << str << endl;  // 結(jié)果未定義,標(biāo)準(zhǔn)僅保證str可析構(gòu)
    cout << "move后:tmp = " << tmp << endl;
    return 0;
}

std::move()之后的原對象,其資源已經(jīng)被轉(zhuǎn)移,處于 “有效但未定義” 的狀態(tài),只能對其進行析構(gòu)或重新賦值,絕對不能再訪問其內(nèi)部資源,否則會出現(xiàn)未定義行為。

4. 左值引用 vs 右值引用

特性左值引用(Type&)右值引用(Type&&)
聲明符號&&&
初始化要求必須初始化,綁定后不可重新綁定必須初始化,綁定后不可重新綁定
直接綁定對象非 const 左值引用:僅左值const 左值引用:左值 + 右值僅右值(純右值 + 將亡值),左值需通過 std::move 轉(zhuǎn)換后綁定
綁定對象的修改性非 const 左值引用:可修改const 左值引用:不可修改可修改綁定的右值對象(const 右值引用除外)
本身屬性左值有名字的右值引用變量是左值,匿名右值引用是右值
核心應(yīng)用場景函數(shù)參數(shù)傳遞、避免拷貝、返回左值對象移動語義、完美轉(zhuǎn)發(fā)、資源所有權(quán)轉(zhuǎn)移
引用折疊與任何引用折疊均為左值引用僅與右值引用折疊為右值引用,其余均為左值引用
能否為 nullptr不能,必須綁定合法對象不能,必須綁定合法對象

四、右值引用的兩大核心應(yīng)用場景

1. 移動語義:告別無意義的深拷貝,實現(xiàn)資源高效轉(zhuǎn)移

移動語義是右值引用最核心的價值,它的核心思想是:對于即將銷毀的對象,不做資源的深拷貝,而是直接將其資源的所有權(quán) “轉(zhuǎn)移” 到新對象中,避免了內(nèi)存分配、數(shù)據(jù)復(fù)制和內(nèi)存釋放的巨大開銷。

移動語義的實現(xiàn),依賴于移動構(gòu)造函數(shù)移動賦值運算符,二者的參數(shù)都是右值引用類型。

1.1 無移動語義的痛點:深拷貝的性能浪費

先實現(xiàn)一個簡單的字符串類,看看沒有移動語義時,臨時對象帶來的性能開銷:

#include <iostream>
#include <cstring>
using namespace std;
class MyString
{
public:
    char* data;
    size_t len;
    // 普通構(gòu)造函數(shù)
    MyString(const char* str)
    {
        len = strlen(str);
        data = new char[len + 1];
        strcpy(data, str);
        cout << "構(gòu)造函數(shù):分配內(nèi)存,地址=" << (void*)data << ",內(nèi)容=" << data << endl;
    }
    // 拷貝構(gòu)造函數(shù)(深拷貝)
    MyString(const MyString& other)
    {
        len = other.len;
        data = new char[len + 1];
        strcpy(data, other.data);
        cout << "拷貝構(gòu)造函數(shù):深拷貝內(nèi)存,新地址=" << (void*)data << ",內(nèi)容=" << data << endl;
    }
    // 拷貝賦值運算符(深拷貝)
    MyString& operator=(const MyString& other)
    {
        if (this == &other)
        {
            return *this;
        }
        // 釋放自身原有資源
        delete[] data;
        // 深拷貝源對象資源
        len = other.len;
        data = new char[len + 1];
        strcpy(data, other.data);
        cout << "拷貝賦值運算符:深拷貝內(nèi)存,新地址=" << (void*)data << ",內(nèi)容=" << data << endl;
        return *this;
    }
    // 析構(gòu)函數(shù)
    ~MyString()
    {
        if (data)
        {
            cout << "析構(gòu)函數(shù):釋放內(nèi)存,地址=" << (void*)data << ",內(nèi)容=" << data << endl;
            delete[] data;
        }
        else
        {
            cout << "析構(gòu)函數(shù):資源已轉(zhuǎn)移,無需釋放" << endl;
        }
    }
};
MyString createString()
{
    MyString str("Hello C++");
    return str; // 返回臨時對象,觸發(fā)拷貝構(gòu)造
}
int main()
{
    MyString str1("Hello World");
    MyString str2 = str1; // 觸發(fā)拷貝構(gòu)造
    MyString str3 = createString(); // 臨時對象觸發(fā)拷貝構(gòu)造,隨后銷毀
    return 0;
}

此處,每一次對象拷貝都會觸發(fā)深拷貝,重新分配內(nèi)存、復(fù)制數(shù)據(jù),臨時對象創(chuàng)建后很快被銷毀,造成了巨大的性能浪費。

1.2 基于右值引用實現(xiàn)移動語義

我們給 MyString 類添加移動構(gòu)造函數(shù)移動賦值運算符,通過右值引用實現(xiàn)資源轉(zhuǎn)移:

#include <iostream>
#include <cstring>
using namespace std;
class MyString
{
public:
    char* data;
    size_t len;
    // 普通構(gòu)造函數(shù)
    MyString(const char* str)
    {
        len = strlen(str);
        data = new char[len + 1];
        strcpy(data, str);
        cout << "構(gòu)造函數(shù):分配內(nèi)存,地址=" << (void*)data << ",內(nèi)容=" << data << endl;
    }
    // 拷貝構(gòu)造函數(shù)(深拷貝)
    MyString(const MyString& other)
    {
        len = other.len;
        data = new char[len + 1];
        strcpy(data, other.data);
        cout << "拷貝構(gòu)造函數(shù):深拷貝內(nèi)存,新地址=" << (void*)data << ",內(nèi)容=" << data << endl;
    }
    // 移動構(gòu)造函數(shù)(右值引用參數(shù),資源轉(zhuǎn)移)
    // noexcept關(guān)鍵字:告訴編譯器該函數(shù)不會拋出異常,STL容器會優(yōu)先調(diào)用
    MyString(MyString&& other) noexcept
    {
        // 直接轉(zhuǎn)移源對象的資源,無需深拷貝
        data = other.data;
        len = other.len;
        // 源對象指針置空,避免析構(gòu)時釋放資源
        other.data = nullptr;
        other.len = 0;
        cout << "移動構(gòu)造函數(shù):轉(zhuǎn)移資源,目標(biāo)地址=" << (void*)data << endl;
    }
    // 拷貝賦值運算符(深拷貝)
    MyString& operator=(const MyString& other)
    {
        if (this == &other)
        {
            return *this;
        }
        delete[] data;
        len = other.len;
        data = new char[len + 1];
        strcpy(data, other.data);
        cout << "拷貝賦值運算符:深拷貝內(nèi)存,新地址=" << (void*)data << ",內(nèi)容=" << data << endl;
        return *this;
    }
    // 移動賦值運算符(右值引用參數(shù),資源轉(zhuǎn)移)
    MyString& operator=(MyString&& other) noexcept
    {
        if (this == &other)
        {
            return *this;
        }
        // 釋放自身原有資源
        delete[] data;
        // 轉(zhuǎn)移源對象資源
        data = other.data;
        len = other.len;
        // 源對象指針置空
        other.data = nullptr;
        other.len = 0;
        cout << "移動賦值運算符:轉(zhuǎn)移資源,目標(biāo)地址=" << (void*)data << endl;
        return *this;
    }
    // 析構(gòu)函數(shù)
    ~MyString()
    {
        if (data)
        {
            cout << "析構(gòu)函數(shù):釋放內(nèi)存,地址=" << (void*)data << ",內(nèi)容=" << data << endl;
            delete[] data;
        }
        else
        {
            cout << "析構(gòu)函數(shù):資源已轉(zhuǎn)移,無需釋放" << endl;
        }
    }
};
MyString createString()
{
    MyString str("Hello C++");
    return str;
}
int main()
{
    MyString str1("Hello World");
    MyString str2 = std::move(str1); // 觸發(fā)移動構(gòu)造,轉(zhuǎn)移str1的資源
    MyString str3 = createString(); // 臨時對象是右值,觸發(fā)移動構(gòu)造
    str3 = MyString("Hello Move"); // 臨時對象觸發(fā)移動賦值
    return 0;
}

此處,移動構(gòu)造 / 移動賦值沒有做任何深拷貝,只是轉(zhuǎn)移了指針的指向,沒有新的內(nèi)存分配,性能得到了提升。

  1. 移動構(gòu)造 / 移動賦值的參數(shù)必須是非 const 右值引用,因為我們需要修改源對象的指針,將其置空
  2. 強烈建議給移動構(gòu)造 / 移動賦值添加noexcept關(guān)鍵字,STL 容器(如 vector)在擴容時,只有移動構(gòu)造被聲明為 noexcept,才會優(yōu)先調(diào)用移動構(gòu)造,否則會保守地調(diào)用拷貝構(gòu)造函數(shù)
  3. 當(dāng)類沒有自定義拷貝構(gòu)造、拷貝賦值、析構(gòu)函數(shù)時,編譯器會自動生成默認的移動構(gòu)造和移動賦值函數(shù);如果自定義了上述函數(shù),編譯器不會自動生成,需要手動實現(xiàn)

1.3 移動語義的常見場景

  1. STL 容器的優(yōu)化:C++11 之后,所有 STL 容器都實現(xiàn)了移動構(gòu)造和移動賦值,使用std::move()轉(zhuǎn)移容器可以避免全量拷貝,比如vector<int> vec2 = std::move(vec1); 時間復(fù)雜度從 O (n) 降到 O (1)
  2. emplace 系列函數(shù)vector::emplace_back()map::emplace()等函數(shù),通過完美轉(zhuǎn)發(fā)直接在容器內(nèi)存中構(gòu)造對象,避免了臨時對象的拷貝和移動,性能優(yōu)于push_back()/insert()
  3. 智能指針的資源轉(zhuǎn)移std::unique_ptr無法拷貝,只能通過移動語義轉(zhuǎn)移所有權(quán),正是基于右值引用實現(xiàn)
  4. 函數(shù)返回大對象:返回大容器、大對象時,編譯器會優(yōu)先進行 RVO/NRVO 返回值優(yōu)化,優(yōu)化失效時會自動調(diào)用移動構(gòu)造,無需手動加std::move()

2. 完美轉(zhuǎn)發(fā)

完美轉(zhuǎn)發(fā)是右值引用的第二大核心應(yīng)用,它的核心目標(biāo)是:在模板函數(shù)中,將參數(shù)原封不動地轉(zhuǎn)發(fā)給另一個函數(shù),保留參數(shù)的左值 / 右值屬性、const/volatile 修飾符。

簡單來說,傳入的是左值,轉(zhuǎn)發(fā)后還是左值;傳入的是右值,轉(zhuǎn)發(fā)后還是右值,不會因為轉(zhuǎn)發(fā)而改變參數(shù)的類型屬性,這就是 “完美” 的含義。

2.1 完美轉(zhuǎn)發(fā)的底層支撐

完美轉(zhuǎn)發(fā)的實現(xiàn),依賴三個 C++11 的核心特性:

  1. 右值引用
  2. 萬能引用(轉(zhuǎn)發(fā)引用)
  3. 引用折疊規(guī)則
  4. std::forward()

2.1.1 萬能引用(轉(zhuǎn)發(fā)引用)

萬能引用是指模板函數(shù)中,形如T&&的參數(shù),它既可以綁定左值,也可以綁定右值,同時保留參數(shù)的所有類型屬性。

注意:只有發(fā)生模板參數(shù)推導(dǎo)時,T&&才是萬能引用;如果是具體類型的Type&&,只是普通的右值引用,只能綁定右值。

// 模板參數(shù)推導(dǎo),T&&是萬能引用
template<typename T>
void func(T&& arg)
{
}

// 具體類型,int&&是普通右值引用
void func2(int&& arg)
{
}

int main()
{
    int a = 10;
    func(a);  // 正確:萬能引用綁定左值
    func(10); // 正確:萬能引用綁定右值
    // func2(a); // 報錯:普通右值引用無法綁定左值
    func2(10); // 正確:普通右值引用綁定右值
    return 0;
}

2.1.2 引用折疊規(guī)則

C++ 不允許引用的引用,但在模板參數(shù)推導(dǎo)時,會出現(xiàn)引用嵌套的情況,此時編譯器會執(zhí)行引用折疊,規(guī)則如下:

  1. 左值引用& 與 任何引用(&/&&)折疊,最終結(jié)果都是左值引用&
  2. 右值引用&& 與 右值引用&& 折疊,最終結(jié)果才是右值引用&&
嵌套類型折疊結(jié)果
T& &T&
T& &&T&
T&& &T&
T&& &&T&&

這個規(guī)則,讓萬能引用可以同時適配左值和右值:

  • 當(dāng)傳入左值int&時,T 被推導(dǎo)為int&,T&& = int& &&,折疊為int&,左值引用
  • 當(dāng)傳入右值int&&時,T 被推導(dǎo)為int,T&& = int&&,右值引用

2.1.3 std::forward () 的作用

std::forward()是實現(xiàn)完美轉(zhuǎn)發(fā)的核心函數(shù),它的作用是有條件地進行類型轉(zhuǎn)換

  • 當(dāng)傳入的是左值時,不做任何轉(zhuǎn)換,依然返回左值
  • 當(dāng)傳入的是右值時,轉(zhuǎn)換為右值引用,返回右值

std::move()的無條件轉(zhuǎn)右值不同,std::forward()是 “按需轉(zhuǎn)換”,完美保留參數(shù)的原始屬性。

2.2 完美轉(zhuǎn)發(fā)的示例

我們通過一個完整的例子,看看完美轉(zhuǎn)發(fā)的效果,以及不使用完美轉(zhuǎn)發(fā)的問題:

#include <iostream>
#include <utility>
using namespace std;
// 重載函數(shù),區(qū)分左值和右值參數(shù)
void process(int& x)
{
    cout << "左值引用版本被調(diào)用,值=" << x << endl;
}
void process(int&& x)
{
    cout << "右值引用版本被調(diào)用,值=" << x << endl;
}
// 不使用完美轉(zhuǎn)發(fā)的模板函數(shù)
template<typename T>
void noForward(T&& arg)
{
    cout << "無完美轉(zhuǎn)發(fā):";
    process(arg); // arg是有名字的變量,本身是左值,永遠調(diào)用左值版本
}
// 使用完美轉(zhuǎn)發(fā)的模板函數(shù)
template<typename T>
void withForward(T&& arg)
{
    cout << "有完美轉(zhuǎn)發(fā):";
    process(std::forward<T>(arg)); // 完美轉(zhuǎn)發(fā),保留原始左值/右值屬性
}
int main()
{
    int a = 5;
    cout << "===== 傳入左值a =====" << endl;
    noForward(a);
    withForward(a);
    cout << "\n===== 傳入右值10 =====" << endl;
    noForward(10);
    withForward(10);
    return 0;
}

運行結(jié)果:

===== 傳入左值a =====
無完美轉(zhuǎn)發(fā):左值引用版本被調(diào)用,值=5
有完美轉(zhuǎn)發(fā):左值引用版本被調(diào)用,值=5

===== 傳入右值10 =====
無完美轉(zhuǎn)發(fā):左值引用版本被調(diào)用,值=10
有完美轉(zhuǎn)發(fā):右值引用版本被調(diào)用,值=10

可以看到,不使用std::forward()時,無論傳入左值還是右值,都會調(diào)用左值版本的 process 函數(shù),因為函數(shù)內(nèi)部的 arg 是有名字的變量,本身是左值。而使用完美轉(zhuǎn)發(fā)后,參數(shù)的左值 / 右值屬性被完美保留,正確調(diào)用了對應(yīng)的重載函數(shù)。

2.3 完美轉(zhuǎn)發(fā)的常見場景

  1. STL 容器的 emplace 系列函數(shù):直接在容器內(nèi)存中構(gòu)造對象,將用戶傳入的參數(shù)完美轉(zhuǎn)發(fā)給對象的構(gòu)造函數(shù),保留所有類型屬性
  2. std::bind/std::function:實現(xiàn)函數(shù)對象的封裝和參數(shù)轉(zhuǎn)發(fā),底層依賴完美轉(zhuǎn)發(fā)
  3. 線程庫 std::thread:將線程函數(shù)的參數(shù)完美轉(zhuǎn)發(fā)到線程執(zhí)行上下文
  4. 泛型編程中的中間轉(zhuǎn)發(fā)函數(shù):需要將參數(shù)透傳給其他函數(shù)的模板場景

五、高頻踩坑點與實踐建議

1. 高頻踩坑點

坑 1:std::move () 之后繼續(xù)使用原對象

std::move()之后,原對象的資源已經(jīng)被轉(zhuǎn)移,處于有效但未定義的狀態(tài),此時訪問其內(nèi)部資源(如 string 的字符、vector 的元素)會觸發(fā)未定義行為,只能對其進行析構(gòu)或重新賦值。

坑 2:對函數(shù)返回值使用 std::move ()

很多人會寫return std::move(obj);,這是典型的錯誤寫法。編譯器默認會對返回的局部對象執(zhí)行 RVO/NRVO 返回值優(yōu)化,直接在返回值地址構(gòu)造對象,零拷貝。而加了std::move()之后,會阻止編譯器的優(yōu)化,強制調(diào)用移動構(gòu)造,反而降低性能。

坑 3:誤以為右值引用變量本身是右值

有名字的右值引用變量,本身是左值,無法直接綁定到右值引用參數(shù)。如果需要在函數(shù)中轉(zhuǎn)發(fā)右值引用形參,必須使用std::move()std::forward()

void func(int&& x)
{
}
void test(int&& arg)
{
    // func(arg); // 報錯:arg是右值引用變量,本身是左值
    func(std::move(arg)); // 正確
}

坑 4:給移動構(gòu)造 / 移動賦值加 const 修飾

移動構(gòu)造 / 移動賦值需要修改源對象的指針,將其置空,加了 const 之后無法修改源對象,只能做深拷貝,完全失去了移動語義的意義。const 右值引用幾乎沒有使用場景,不如直接用 const 左值引用。

坑 5:把普通右值引用當(dāng)成萬能引用

只有在模板參數(shù)推導(dǎo)場景下的T&&才是萬能引用,類成員函數(shù)的模板參數(shù)如果沒有推導(dǎo)過程,也不是萬能引用。

class Test
{
public:
    // 這里的T&&不是萬能引用,因為類實例化時T已經(jīng)確定,沒有參數(shù)推導(dǎo)
    template<typename T>
    void func(T&& arg);
};

2. 實踐建議

  1. 只在需要轉(zhuǎn)移資源的時候使用std::move(),且確保 move 之后的對象不再使用
  2. 實現(xiàn)移動構(gòu)造 / 移動賦值時,必須添加noexcept關(guān)鍵字,確保 STL 容器能優(yōu)先調(diào)用
  3. 函數(shù)返回局部對象時,不要加std::move(),交給編譯器做返回值優(yōu)化
  4. 泛型編程中,需要轉(zhuǎn)發(fā)參數(shù)時,使用std::forward()實現(xiàn)完美轉(zhuǎn)發(fā),不要用std::move()
  5. 遵循 “三五法則”:如果自定義了析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、拷貝賦值運算符中的任意一個,就應(yīng)該同時實現(xiàn)或禁用移動構(gòu)造、移動賦值運算符
  6. 對于不需要拷貝、只需要轉(zhuǎn)移所有權(quán)的對象(如 unique_ptr、文件句柄),禁用拷貝構(gòu)造和拷貝賦值,只實現(xiàn)移動構(gòu)造和移動賦

到此這篇關(guān)于C++左值引用、右值引用應(yīng)用場景分析的文章就介紹到這了,更多相關(guān)C++左值引用、右值引用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++重載運算符的規(guī)則詳解

    C++重載運算符的規(guī)則詳解

    運算符重載函數(shù)可以是類的成員函數(shù),也可以是類的友元函數(shù),還可以是既非類的成員函數(shù)也不是友元函數(shù)的普通函數(shù)
    2013-10-10
  • C++簡單又好用的基本運算符重載

    C++簡單又好用的基本運算符重載

    繼友元知識過后,就到了今天的C++運算符重載的內(nèi)容了,運算符重載是C++里比較重要的內(nèi)容。這篇博文不會一下子講完各種運算符重載,因為太多了了也不好吸收掌握,所以運算符重載我準(zhǔn)備分多次記錄和分享,那么接下來進入正文
    2022-06-06
  • 用C語言實現(xiàn)圣誕樹(簡易版+進階版)

    用C語言實現(xiàn)圣誕樹(簡易版+進階版)

    大家好,本篇文章主要講的是用C語言實現(xiàn)圣誕樹(簡易版+進階版),感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • C++編程中new運算符的使用學(xué)習(xí)教程

    C++編程中new運算符的使用學(xué)習(xí)教程

    這篇文章主要介紹了C++編程中new運算符的使用學(xué)習(xí)教程,是C++入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2016-01-01
  • C++中構(gòu)造函數(shù)與析構(gòu)函數(shù)的調(diào)用順序詳解

    C++中構(gòu)造函數(shù)與析構(gòu)函數(shù)的調(diào)用順序詳解

    C++ 語言一直被批評太復(fù)雜了,很多細節(jié)的地方需要仔細推敲,甚至其構(gòu)造函數(shù)和析構(gòu)的調(diào)用順序也成為了一個讓人迷惑的問題,在此我做了簡單的總結(jié)。這篇文章主要介紹了C++中構(gòu)造函數(shù)與析構(gòu)函數(shù)的調(diào)用順序,需要的朋友可以參考借鑒。
    2017-01-01
  • C++實現(xiàn)LeetCode(23.合并k個有序鏈表)

    C++實現(xiàn)LeetCode(23.合并k個有序鏈表)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(23.合并k個有序鏈表),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C語言調(diào)用Python代碼的方法

    C語言調(diào)用Python代碼的方法

    這篇文章主要介紹了C語言調(diào)用Python代碼的方法,文中講解非常細致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • C++中的函數(shù)修飾符深入講解

    C++中的函數(shù)修飾符深入講解

    這篇文章主要給大家介紹了關(guān)于C++中函數(shù)修飾符的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • MFC實現(xiàn)學(xué)生選課系統(tǒng)

    MFC實現(xiàn)學(xué)生選課系統(tǒng)

    這篇文章主要為大家詳細介紹了MFC實現(xiàn)學(xué)生選課系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-02-02
  • C++利用GPAC實現(xiàn)生成MP4文件的示例代碼

    C++利用GPAC實現(xiàn)生成MP4文件的示例代碼

    GPAC主要針對學(xué)生和內(nèi)容創(chuàng)作者,代表了一個跨平臺的多媒體框架,開發(fā)人員可以使用它在?LGPL?許可下制作開源媒體。本文就來用GPAC實現(xiàn)生成MP4文件,感興趣的可以了解一下
    2023-02-02

最新評論

同江市| 迁西县| 富锦市| 顺平县| 财经| 洛扎县| 敖汉旗| 长岛县| 西平县| 湘阴县| 措美县| 曲水县| 长宁县| 衡阳市| 大丰市| 文成县| 枣强县| 富顺县| 临湘市| 新野县| 石城县| 突泉县| 安宁市| 泰和县| 张掖市| 天水市| 区。| 从化市| 乌海市| 资阳市| 武城县| 乌鲁木齐县| 镇宁| 东乡县| 成安县| 金沙县| 潢川县| 左贡县| 军事| 丽水市| 博罗县|