真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

【C++11】三大神器之——右值、移動語義、完美轉(zhuǎn)發(fā)-創(chuàng)新互聯(lián)

前言

如果你還不知道C++11引入的右值、移動語義、完美轉(zhuǎn)發(fā)是什么,可以閱讀這篇文章;如果你已經(jīng)對這些知識了如指掌,也可以看看有什么可以補充~😏

創(chuàng)新互聯(lián)公司2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元新樂做網(wǎng)站,已為上家服務(wù),為新樂各地企業(yè)和個人服務(wù),聯(lián)系電話:18982081108一、右值 值類別vs變量類型

在正式認識右值之前,我們要先區(qū)分值的類別和變量類型:

  • 值 (value)變量 (variable)是兩個獨立的概念。值不一定擁有變量名(如表達式:i + j + k)。
  • 值只有類別(category)之分,而變量只有類型(type)之分。

值類別可以被劃分左值右值。

那什么是左值和右值呢?左值是能被取地址、不能被移動的值。右值是表達式中間結(jié)果/函數(shù)返回值(可能擁有變量名,也可能沒有)。

有一個可以區(qū)分左值和右值的便捷方法:看能不能對表達式取地址,如果能,則為左值,否則為右值。

C++11擴展了右值的概念,將右值分為了純右值和將亡值,但本文不作討論。

如下的示例將幫助我們區(qū)分左值和右值:

int i = 3;       // i是左值,3是右值
int j = i+8;    // j是左值,i+8是右值
char a = getCh();   // a是左值 ,getCh()的返回值是右值(臨時變量)
左值引用、右值引用、常引用

在以前的文章中,我們曾經(jīng)討論過左值引用和常引用的區(qū)別。在本篇文章中,我們需要進一步系統(tǒng)的了解它們?nèi)咧g的關(guān)系。

引用類型 可以分為兩種:

  • 左值引用:用&符號引用左值(但不能引用右值),
  • 右值引用:用&&符號引用右值(可以移動左值)。

在C++11中,因為增加了右值引用(rvalue reference)的概念,所以C++98中的引用都稱為了左值引用(lvalue reference)。

使用方法如下所示:

int&& a = 3;         // 3是右值,a是右值引用
int b = 8;               // b是左值
int& bb = b;			//bb是左值引用
int&& c = b + 5;   //  b+5是右值,c是右值引用
AA&& aa = getTemp();   // getTemp()的返回值是右值(臨時變量)

左值引用十分常見,我們知道是給變量取個別名,但是引入右值引用的意義是什么呢?(將在下文中解答)

在上述的代碼中,getTemp()的返回值本來在表達式語句結(jié)束后,其生命也就該終結(jié)了(因為是臨時變量),而通過右值引用重獲了新生,其生命周期將與右值引用類型變量aa的生命周期一樣,只要aa還活著,該右值臨時變量將會一直存活下去。

在下面的代碼中將幫助我們區(qū)分左值引用和右值引用:

void func(T& a);//1,參數(shù)是左值
void func(T&& a);//2,參數(shù)是右值
//T類型的變量
T var;
T& rvar1 = var;//正確,rvar1是左值
T& rvar1 = T{};//錯誤,左值引用不能引用右值
T&& rvar2 = T{};//正確,rvar2是右值
T&& rvar2 = var;//錯誤,右值引用不能引用左值
T&& rvar2 = std::move(var);//正確,可以通過std::move()將左值轉(zhuǎn)為右值引用

func(var);//進入1,a是左值
func(T{});//進入2,a是右值
func(rvar1);//進入1,a是左值
func(rvar2);//進入1,rvar2是右值引用但a是左值

可以看出:

  • 當左值引用變量rvar1在初始化時,不能綁定右值T{},
  • 當右值引用變量rvar2在初始化時,不能綁定左值var,但是可以通過std::move()將左值轉(zhuǎn)為右值引用。
  • 在代碼的最后,右值引用變量rvar2作為實參傳入func中時,在作用域內(nèi)是左值(已命名的右值引用是左值)。

另外,C++還支持了常引用,能夠同時接受左值和右值(作為常引用)。

void func(const T& a);//a是常引用

常引用和右值引用 都能接受右值的綁定,有什么區(qū)別呢?

  • 常引用可以像右值引用一樣將右值的生命期延長,但它有一個缺點是,只能讀不能改。

現(xiàn)在回到我們的問題:引入右值引用的意義是什么?

如果函數(shù)重載能夠同時接受:右值引用/常引用參數(shù),則編譯器將優(yōu)先重載:右值引用參數(shù),即引入右值引用的主要目的是實現(xiàn)移動語義。

下面是不同值作為實參傳入形參時,函數(shù)重載優(yōu)先級(數(shù)字越小優(yōu)先級越高):

實參/形參T&const T&T&&const T&&
左值12
常左值1
右值312
常右值21
引用折疊

在正式學習移動語義(move semantic)完美轉(zhuǎn)發(fā)std::forward()之前,我們還要提一嘴引用折疊(reference collapsing),它是移動語義和完美轉(zhuǎn)發(fā)的實現(xiàn)基礎(chǔ)。

using Lref = Data&;
using Rref = Data&&;
Data data;

Lref&  r1 = data;    // r1是左值
Lref&& r2 = data;    // r2是左值
Rref&  r3 = data;    // r3是左值
Rref&& r4 = Data{};  // r4是右值

總之,只有右值引用折疊到右值引用上仍然是一個右值引用,而其他所有的引用類型之間的折疊都將變成左值引用。

二、移動語義 為什么需要移動語義

我們知道,如果一個對象中有堆區(qū)資源,需要編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),實現(xiàn)深拷貝。如果被拷貝的對象是臨時的,拷貝完就沒什么用了,這樣會造成沒有意義的資源申請和釋放操作。如果能將對象包含的資源,直接從舊對象移動到新對象,就可以節(jié)省資源申請和釋放的時間。C++11新增加的移動語義(move semantic)就是為了做到這一點(基本類型不包含資源,其移動和拷貝相同。)。

還有另一種情況:如果資源對象本身不可拷貝(如智能指針std::unique_ptr)需要定義移動構(gòu)造/移動賦值函數(shù),其原理類似。

實現(xiàn)移動語義要增加兩個函數(shù):移動構(gòu)造函數(shù)和移動賦值函數(shù)。我們通過實現(xiàn)一個簡單的string類對象來說明:

class String
{public:
    String()
    {cout<< "String類"<< this<< "的構(gòu)造函數(shù)"<< endl;// 顯示自己被調(diào)用的日志
        const char* s = "Hello C++";
        int len = strlen(s);
        _str = new char[len + 1];
        strcpy(_str, s);
    }

    String(const String& another)
    {cout<< "String類"<< this<< "的拷貝構(gòu)造"<< endl;// 顯示自己被調(diào)用的日志
        int len = strlen(another._str);//獲取源對象中字符串的長度
        _str = new char[len + 1];
        strcpy(_str, another._str);// 把數(shù)據(jù)從源對象中拷貝過來
    }

    String& operator=(String& another)
    {cout<< "String類"<< this<< "的拷貝賦值"<< endl;// 顯示自己被調(diào)用的日志
        if (this == &another)
            return *this;// 避免自我賦值
        int len = strlen(another._str);//獲取源對象中字符串的長度
        _str = new char[len + 1];
        strcpy(_str, another._str);// 把數(shù)據(jù)從源對象中拷貝過來
        return *this;
    }

    String(String&& another)noexcept
    {cout<< "String類"<< this<< "的移動構(gòu)造"<< endl;// 顯示自己被調(diào)用的日志
        if(_str != nullptr)
            delete[] _str;  //如果已分配內(nèi)存,先釋放掉
        this->_str = another._str;// 把資源從源對象中轉(zhuǎn)移過來
        another._str = nullptr;// 把源對象中的指針置空
    }

    String& operator=(String&& another)
    {cout<< "String類"<< this<< "的移動賦值"<< endl;// 顯示自己被調(diào)用的日志
        if (this == &another)
            return *this;// 避免自我賦值
        if(_str != nullptr)
            delete[] _str; //如果已分配內(nèi)存,先釋放掉
        this->_str = another._str;// 把資源從源對象中轉(zhuǎn)移過來
        another._str = nullptr;// 把源對象中的指針置空
        return *this;
    }

    friend ostream& operator<<(ostream& out, String& str)
    {out<< str._str;
        return out;
    }

    ~String()
    {cout<< "String類"<< this<< "的析構(gòu)函數(shù)"<< endl;// 顯示自己被調(diào)用的日志
        if(_str != nullptr)
        {delete[] _str;
            _str = nullptr;
        }
    }

private:
    char* _str;
};

上述代碼中,編譯器會根據(jù)傳入的實參的優(yōu)先級(詳見上文表格),來決定重載的構(gòu)造函數(shù)。

  • 當實參是左值時,使用拷貝構(gòu)造,拷貝源對象所有的元素。
  • 當實參是右值時,使用移動構(gòu)造,將指向源對象的內(nèi)存空間的指針“移動”到新對象,并將源對象的指針置空。
  • 拷貝/移動賦值函數(shù)的原理相同,在此不再過多描述。

測試用例及輸出的結(jié)果:

int main()
{{String str1{};//String類0x751f9ffd60的構(gòu)造函數(shù)
        cout<< "str1 = "<< str1<< endl;//str1 = Hello C++

        String str2{str1};//String類0x751f9ffd58的拷貝構(gòu)造
        cout<< "str2 = "<< str2<< endl;//str2 = Hello C++
		
		//返回一個右值(臨時對象)的lambda函數(shù)
        auto f = [] {String aa; return aa;}; 

        // String str3 = f();//拷貝省略
        // cout<< "str3 = "<< str3<< endl;

        String str{};//String類0x751f9ffd48的構(gòu)造函數(shù)
        String str3{move(str)};//String類0x751f9ffd40的移動構(gòu)造
        cout<< "str3 = "<< str3<< endl;//str3 = Hello C++

        String str4{};//String類0x751f9ffd38的構(gòu)造函數(shù)
		//String類0x751f9ffd68的構(gòu)造函數(shù)
        str4 = f();//String類0x751f9ffd38的移動賦值
        //String類0x751f9ffd68的析構(gòu)函數(shù)
        cout<< "str4 = "<< str4<< endl;//str4 = Hello C++
    }
    //String類0x751f9ffd38的析構(gòu)函數(shù)
    //String類0x751f9ffd40的析構(gòu)函數(shù)
    //String類0x751f9ffd48的析構(gòu)函數(shù)
    //String類0x751f9ffd58的析構(gòu)函數(shù)
    //String類0x751f9ffd60的析構(gòu)函數(shù)
    system("pause");
    return 0;
}

盡管C++11引入了移動語義,但是仍有優(yōu)化的空間——與其調(diào)用一次沒有意義的移動構(gòu)造函數(shù),不如讓編譯器直接跳過這個過程——于是就有了拷貝省略(copy elision)。

移動語義和拷貝省略的區(qū)別:

  • 移動語義是語言標準提出的概念。是通過編寫遵守移動語義的移動構(gòu)造函數(shù)、右值限定成員函數(shù),在邏輯上優(yōu)化對象內(nèi)資源的轉(zhuǎn)移流程。
  • 拷貝省略是非標準(C++ 17 前)的編譯器優(yōu)化。跳過移動/拷貝構(gòu)造函數(shù),讓編譯器直接在移動后的對象內(nèi)存上,構(gòu)造被移動的對象。

由于拷貝省略的存在,在上述代碼中,String str3 = f();會被編譯器優(yōu)化,為了方便演示移動構(gòu)造函數(shù),我們使用了std::move()的方法移動返回值,當然這會造成不必要的開銷。

三、完美轉(zhuǎn)發(fā)

閱讀本節(jié)需要讀者有一定的模板編程基礎(chǔ)。

通用引用

C++11中引入了變長模板的概念,允許向模板參數(shù)里傳入不同類型的不定長引用參數(shù)。由于每個類型可能是左值引用或右值引用,針對所有可能的左右值引用組合,特化所有模板 是不現(xiàn)實的。

如果沒用通用引用的概念,那么對于一個變長模板函數(shù),至少需要兩個重載:

templatevoid func(T& arg, Args&...args)
{func(args...);//左值直接展開
}

templatevoid func(T&& arg, Args&&...args)
{func(std::move(args...));//右值需要std::move()轉(zhuǎn)發(fā)
}

Scott Meyers(Effective Modern C++的作者)指出“有時候符號&&并不一定代表右值引用,它也可能是左值引用?!?/p>

事實上,如果一個引用符號需要通過推導才能得出左右值的類型(如模板參數(shù)類型或者auto),那么這個符號就可以是左值引用或右值引用——這就是通用引用 (universal reference)

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

這一點我們通過上文中的引用折疊的示例也可以得出。

基于通用引用,我們可以對上述的代碼進行改進:

templatevoid func(T&& arg, Args&&...args)
{func(std::forward(args)... );
}

其中std::forward實現(xiàn)了針對左右值的參數(shù),能保證被轉(zhuǎn)發(fā)參數(shù)的左、右值屬性不變,即完美轉(zhuǎn)發(fā)(perfect forwarding)

在C++11中,完美轉(zhuǎn)發(fā)支持:

  • 如果模板中(包括類模板和函數(shù)模板)函數(shù)的參數(shù)書寫成為T&& 參數(shù)名,那么,函數(shù)既可以接受左值引用,又可以接受右值引用。
  • 提供了模板函數(shù)std::forward(參數(shù)),用于轉(zhuǎn)發(fā)參數(shù),如果 參數(shù)是一個右值,轉(zhuǎn)發(fā)之后仍是右值引用;如果參數(shù)是一個左值,轉(zhuǎn)發(fā)之后仍是左值引用。

使用示例如下:

void fun1(int& i)//如果參數(shù)是左值
{cout<< "左值 = "<< i<< endl;
}

void fun1(int&& i)//如果參數(shù)是右值
{cout<< "右值 = "<< i<< endl;
}
templatevoid func(T&& ii)
{fun1(std::forward(ii));
}

完美轉(zhuǎn)發(fā)的語法比較簡單,至于其原理本文暫時不深入研究。😝

最后

本文部分參考自文章

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


標題名稱:【C++11】三大神器之——右值、移動語義、完美轉(zhuǎn)發(fā)-創(chuàng)新互聯(lián)
鏈接分享:http://weahome.cn/article/cdessi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部