日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++ 11 中的右值引用

發(fā)布時(shí)間:2024/8/23 c/c++ 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++ 11 中的右值引用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

C++ 11 中的右值引用

右值引用的功能

首先,我并不介紹什么是右值引用,而是以一個(gè)例子里來介紹一下右值引用的功能:

????#include?<iostream>
????#include?<vector>
????using?namespace?std;

????class?obj
????{
????public?:
????????obj() { cout <<?">> create obj "?<< endl; }
????????obj(const?obj& other) { cout <<?">> copy create obj "?<< endl; }
????};

????vector<obj> foo()
????{
????????vector<obj> c;
????????c.push_back(obj());

????????cout <<?"---- exit foo ----"?<< endl;
????????return?c;
????}

????int?main()
????{
????????vector<obj> k;
????????k = foo();
????}

首先我們編譯一下這個(gè)函數(shù),運(yùn)行結(jié)果如下:

????tianfang > g++ main.cpp
????tianfang > a.out
????>> create obj?
????>> copy create obj?
????---- exit foo ----
????>> copy create obj?
????tianfang >

可以看到,對obj對象執(zhí)行了兩次構(gòu)造。vector是一個(gè)常用的容器了,我們可以很容易的分析這這兩次拷貝構(gòu)造的時(shí)機(jī):

  • foo函數(shù)第二行,調(diào)用push_back的時(shí)候,會在vector里建立一個(gè)obj的副本
  • main函數(shù)第二行,執(zhí)行復(fù)制函數(shù)的時(shí)候,會把foo()返回的對象全部復(fù)制過來,再次執(zhí)行一次拷貝構(gòu)造
  • 由于對象的拷貝構(gòu)造的開銷是非常大的,因此我們想就可能避免他們。其中,第一次拷貝構(gòu)造是vector的特性所決定的,不可避免。但第二次拷貝構(gòu)造,在C++ 11中就是可以避免的了。

    ????tianfang > g++?-std=c++11?main.cpp
    ????tianfang > a.out
    ????>> create obj?
    ????>> copy create obj?
    ????---- exit foo ----
    ????tianfang >

    可以看到,我們除了加上了一個(gè)-std=c++11選項(xiàng)外,什么都沒干,但現(xiàn)在就把第二次的拷貝構(gòu)造給去掉了。它是如何實(shí)現(xiàn)這一過程的呢?

    在老版本中,當(dāng)我們執(zhí)行第二行的賦值操作的時(shí)候,執(zhí)行過程如下:

  • foo()函數(shù)返回一個(gè)臨時(shí)對象(這里用~tmp來標(biāo)識它)
  • 執(zhí)行vector的 '=' 函數(shù),將對象k中的現(xiàn)有成員刪除,將~tmp的成員復(fù)制到k中來
  • 刪除臨時(shí)對象~tmp
  • 在C++11的版本中,執(zhí)行過程如下:

  • foo()函數(shù)返回一個(gè)臨時(shí)對象(這里用~tmp來標(biāo)識它)
  • 執(zhí)行vector的 '=' 函數(shù),將對象k中的成員~tmp的成員互換,此時(shí)k中的成員就被替換成了~tmp中的成員
  • 刪除臨時(shí)對象~tmp(此時(shí)就刪除了以前的k中的成員)
  • 關(guān)鍵的過程就是第2步,它不是復(fù)制而是交換,從而避免的成員的拷貝,但效果卻是一樣的。不用修改代碼,性能卻得到了提升,對于程序員來說就是一份免費(fèi)的午餐。

    但是,這份免費(fèi)的午餐也不是無條件就可以獲取的,帶上-std=c++11編譯時(shí),如果使用STL代碼可以享用這份午餐,但如果使用我們以前的老代碼發(fā)現(xiàn)還是和以前的功能是一樣的,那么,如何讓我們以前的代碼也能得到這個(gè)效率的提升呢?

    ? ?

    通過交換減少數(shù)據(jù)的拷貝

    為了演示如何在我們的代碼中也獲取這個(gè)性能提升,首先我先寫了一個(gè)山寨的vector:

    ????#include?<iostream>
    ????#include?<vector>
    ????using?namespace?std;

    ????class?obj
    ????{
    ????public?:
    ????????obj() { cout <<?">> create obj "?<< endl; }
    ????????obj(const?obj& other) { cout <<?">> copy create obj "?<< endl; }
    ????};

    ????template?<class?T>
    ????class?container
    ????{
    ????public:
    ????????T* value;

    ????public:
    ????????container() : value(NULL) {};
    ????????~container() {?delete?value; }?

    ????????container(const?container& other)
    ????????{
    ????????????value =?new?T(*other.value);
    ????????}

    ????????const?container&?operator?= (const?container& other)
    ????????{
    ????????????delete?value;
    ????????????value =?new?T(*other.value);
    ????????????return?*this;
    ????????}

    ????????void?push_back(const?T& item)
    ????????{
    ????????????delete?value;
    ????????????value =?new?T(item);
    ????????}
    ????};

    ????container<obj> foo()
    ????{
    ????????container<obj> c;
    ????????c.push_back(obj());

    ????????cout <<?"---- exit foo ----"?<< endl;
    ????????return?c;
    ????}

    ????int?main()
    ????{
    ????????container<obj> k ;
    ????????k = foo();????
    ????}

    這個(gè)vector只能容納一個(gè)元素,但并不妨礙我們的演示,其功能和前面的例子是一樣的,運(yùn)行這段代碼,結(jié)果如下:

    ????tianfang > make
    ????g++?-std=c++11?main.cpp
    ????tianfang > a.out
    ????>> create obj?
    ????>> copy create obj?
    ????---- exit foo ----
    ????>> copy create obj?
    ????tianfang >

    如前所述,仍然有兩次拷貝構(gòu)造。其實(shí)前面已經(jīng)說過交換實(shí)現(xiàn)減少拷貝構(gòu)造的原理,那么,我們可以通過修改 '=' 函數(shù)來手動實(shí)現(xiàn)這一過程。

    ????const?container& operator = (container&?other)
    ????{
    ????????T* tmp = value;
    ????????value =?other.value;
    ????????other.value = tmp;
    ????????return?*this;
    ????}

    在VC中運(yùn)行這段代碼,發(fā)現(xiàn)運(yùn)行結(jié)果和預(yù)期一致,

    ????>> create obj?
    ????>> copy create obj?
    ????---- exit foo ----

    但是,gcc中卻無法通過編譯,原因很簡單:gcc期望的賦值函數(shù)的參數(shù)是const型的,而這里為了交換成員,而不能使用const型。

    那么,雖然gcc中不能生效,是否可以說在vc中就可以以這種形式獲取性能提升呢?答案是否定的。雖然在這段代碼中這么寫沒有問題,但賦值函數(shù)本身是期望復(fù)制功能的,而不是交換。例如,修改后下面的運(yùn)行結(jié)果就不對了。

    ????int?main()
    ????{
    ????????container<obj> k, k2;
    ????????k = foo();????

    ????????//
    預(yù)期結(jié)果是復(fù)制,但執(zhí)行了交換
    ????????k2 = k;
    ????}

    gcc的告警是有道理的:如果 '=' 函數(shù)實(shí)現(xiàn)的是復(fù)制功能,雖然效率低點(diǎn),但保證了功能正確,但如果實(shí)現(xiàn)的是交換的功能,則不能保證功能一定正確。只有當(dāng) '=' 函數(shù)右邊的對象為一個(gè)臨時(shí)變量的時(shí)候,由于臨時(shí)變量會馬上被刪除掉,此時(shí)的交換和復(fù)制的效果是一樣的。其實(shí)VC也應(yīng)該把這個(gè)告警加上才合適。

    PS:對臨時(shí)變量定義和來源不清楚的朋友可以參考一下這篇文章。

    現(xiàn)在的問題是:我們無法在賦值函數(shù)里區(qū)分傳入的是一個(gè)臨時(shí)對象還是非臨時(shí)對象,因此只能執(zhí)行復(fù)制操作。為了解決這一問題,c++中引入了一個(gè)新的賦值函數(shù)的重載形式:

    ????container& operator = (container&&?other)

    這個(gè)賦值函數(shù)通常稱為移動賦值函數(shù),和老版本的相比,它有兩點(diǎn)區(qū)別:

  • 入?yún)⒉皇莄onst型,因此它是可以更改入?yún)⒌闹档?#xff0c;從而實(shí)現(xiàn)交換操作
  • 入?yún)⑶懊嬗袃蓚€(gè)&號,這個(gè)是C++11引入的新語法,稱為右值引用,它的使用方式和普通引用是一樣的,唯一的區(qū)別是可以指向臨時(shí)變量。
  • 現(xiàn)在,我們就有兩個(gè)版本的賦值函數(shù)了,C++11在語法級別也做了適應(yīng):

    • 如果入?yún)⑹桥R時(shí)變量,則執(zhí)行移動賦值函數(shù),如果沒有定義移動賦值函數(shù),則執(zhí)行復(fù)制賦值函數(shù)(以保證老版本代碼能編譯通過)
    • 如果入?yún)⒉皇桥R時(shí)變量,則執(zhí)行普通的復(fù)制賦值函數(shù)

    現(xiàn)在,我們實(shí)現(xiàn)一下山寨版的移動賦值函數(shù):

    ????container& operator = (container&&?other)
    ????{
    ????????delete?value;
    ????????value =?other.value;
    ????????other.value =?NULL;
    ????????return?*this;

    ????}

    運(yùn)行后結(jié)果就和我們期望的那樣,避免了成員的第二次的拷貝構(gòu)造。

    和移動賦值函數(shù)相應(yīng)的,也有一個(gè)一個(gè)移動構(gòu)造函數(shù),也最好實(shí)現(xiàn)以下:

    ????container (container&&?other)
    ????{
    ????????value =?other.value;
    ????????other.value =?NULL;
    ????}

    我們也可以實(shí)現(xiàn)自己的右值引用版的重載函數(shù),這里就不多介紹了。

    注意:本文所示的代碼只是為了演示和實(shí)現(xiàn)右值引用,力求簡潔,并沒有寫得很完善(一個(gè)典型的缺失就是在賦值函數(shù)中沒有判斷入?yún)⑹欠袷潜旧?#xff09;,請不要將其應(yīng)用于項(xiàng)目中。

    完善的版本請看MSDN文章:如何編寫一個(gè)移動構(gòu)造函數(shù),其相應(yīng)的對右值引用的介紹文章Rvalue引用聲明:&&也非常值得一讀。

    ?

    通過std::move函數(shù)顯式使用交換

    首先看一下這段代碼:

    ????class?bigobj
    ????{
    ????public?:
    ????????bigobj() { cout <<?">> create obj "?<< endl; }
    ????????bigobj(const?bigobj&?other) { cout <<?">> copy create obj "?<< endl; }
    ????????bigobj(bigobj&&?other) { cout <<?">> move create obj "?<< endl; }
    ????};

    ????int?main()
    ????{
    ????????list<bigobj> list;
    ????????for(int?i = 0; i < 3; i++)
    ????????{
    ????????????bigobj?obj;
    ????????????list.push_back(obj);
    ????????}
    ????}

    運(yùn)行的時(shí)候就會發(fā)現(xiàn):雖然我們定義了移動構(gòu)造函數(shù),但是它仍然會執(zhí)行拷貝構(gòu)造函數(shù)。這是因?yàn)榫幾g器并不認(rèn)為obj是臨時(shí)變量。關(guān)于什么變量才是臨時(shí)變量,前文已經(jīng)給了個(gè)鏈接來說明它,簡單的說,我們能夠看到的命名變量都不是臨時(shí)變量

    雖然obj對象不是語言級別的臨時(shí)變量,但是從功能上來看,它就是一個(gè)臨時(shí)變量,是可以使用移動構(gòu)造函數(shù)來消除拷貝帶來的性能損失的。為了解決這一問題,C++提供了一個(gè)move函數(shù)來把obj變量強(qiáng)制轉(zhuǎn)換為右值引用,這樣就可以使用移動構(gòu)造函數(shù)了。

    ????for(int?i = 0; i < 3; i++)
    ????{
    ????????bigobj?obj;
    ????????list.push_back(
    std::move(obj));
    ????}

    不過,需要注意的是,和系統(tǒng)識別的臨時(shí)變量而自動使用右值引用不同,這種強(qiáng)制轉(zhuǎn)換是有一定的風(fēng)險(xiǎn)的,由于在push_back后執(zhí)行了交換操作,如果再次使用它會出現(xiàn)非預(yù)期的結(jié)果,只有能確定該變量不會再次被使用才能執(zhí)行這種轉(zhuǎn)換。

    總結(jié)

    以上是生活随笔為你收集整理的C++ 11 中的右值引用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。