亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++精要分析右值引用與完美轉(zhuǎn)發(fā)的應(yīng)用

 更新時(shí)間:2022年05月09日 10:29:45   作者:程序猿阿諾  
C++11標(biāo)準(zhǔn)為C++引入右值引用語(yǔ)法的同時(shí),還解決了一個(gè)短板,即使用簡(jiǎn)單的方式即可在函數(shù)模板中實(shí)現(xiàn)參數(shù)的完美轉(zhuǎn)發(fā)。那么,什么是完美轉(zhuǎn)發(fā)?它為什么是C++98/03 標(biāo)準(zhǔn)存在的一個(gè)短板?C++11標(biāo)準(zhǔn)又是如何為C++彌補(bǔ)這一短板的?別急,本節(jié)將就這些問(wèn)題給讀者做一一講解

區(qū)分左值與右值

在C++面試的時(shí)候,有一個(gè)看起來(lái)似乎挺簡(jiǎn)單的問(wèn)題,卻總可以挖出坑來(lái),就是問(wèn):“如何區(qū)分左值與右值?”

如果面試者自信地回答:“簡(jiǎn)單來(lái)說(shuō),等號(hào)左邊的就是左值,等號(hào)右邊的就是右值。” 那么好了,手寫(xiě)一道面試題繼續(xù)提問(wèn)。

int a=1;
int b=a;

問(wèn):a和b各是左值還是右值?

b是左值沒(méi)有疑問(wèn),但如果說(shuō)a在上面是左值,在下面是右值的,那就要面壁思過(guò)了。C++從來(lái)就不是一門可以淺嘗輒止的編程語(yǔ)言,要學(xué)好它真的需要不斷地去探問(wèn)。公布答案:上面代碼中的a和b都是左值。所以在很多地方都能看到的區(qū)分左右值說(shuō)法是并不準(zhǔn)確的。

如果是給出描述性的說(shuō)明,那么左值就是指向特定內(nèi)存具有名稱的值(具名對(duì)象),它有一個(gè)相對(duì)穩(wěn)定的內(nèi)存地址,并且有一段較長(zhǎng)的生命周期。右值是不指向穩(wěn)定內(nèi)存地址的匿名值(不具名對(duì)象),它的生命周期很短,通常是暫時(shí)性的。

要是看著上面這段說(shuō)明有些抽象,那還有一個(gè)好辦法來(lái)幫助區(qū)分,那就是是否可以用取地址符“&”來(lái)獲得地址。如果能取到地址的則為左值,否則編譯期都報(bào)錯(cuò)的,那就是右值。

還是以上面的代碼為例,&a; &b;這個(gè)一眼能看出來(lái)可以取地址成功,這是左值。而&1這樣的寫(xiě)法編譯器肯定會(huì)報(bào)錯(cuò),所以1是右值。用這樣的方法,目測(cè)也可以判斷出來(lái)了。

右值引用

說(shuō)到C++中的引用,相信大家都很熟悉其用法了。在函數(shù)調(diào)用時(shí)需要對(duì)變量進(jìn)行修改,或者避免內(nèi)存復(fù)制,就會(huì)使用引用的方式。當(dāng)然,使用指針也能達(dá)到一樣的效果,但引用相對(duì)來(lái)說(shuō)更為安全可靠。這種使用方式就是左值引用。

那么好了,我們先從語(yǔ)法上來(lái)認(rèn)識(shí)一下右值引用。

int i = 0;
int &j = i; //左值引用
int &&k = 10; //右值引用

我們看到,右值引用的寫(xiě)法就是在變量名前加上"&&"標(biāo)識(shí)。它的作用是可以延長(zhǎng)字面量數(shù)字10的生命周期。不過(guò),這看起來(lái)似乎并沒(méi)什么用,不像左值引用那樣已經(jīng)深入人心。那么,我們接下來(lái)看一段有意義的示例代碼。

#include        <iostream>
using namespace std;
static const int DataSize = 1024;
class ActOne {
    public:
        ActOne() { cout << "ActOne default construct" << endl; }
        ActOne(const ActOne &one) { cout << "ActOne copy construct" << endl; }
        ~ActOne() { cout << "ActOne destructor" << endl;}
        void DoSomething() { cout << "ActOne work" << endl; }
};
ActOne make_one() {
    ActOne one;
    return one;
}
int main() {
    ActOne one = make_one();
    one.DoSomething();
    cout << "++++++++++" << endl;
    ActOne &&one2 = make_one();
    one2.DoSomething();
}

上述源碼就是實(shí)現(xiàn)生成一個(gè)對(duì)象并返回的功能。需要注意的是,如果使用g++編譯器,對(duì)這段代碼進(jìn)行編譯的時(shí)候要加上-fno-elide-constructors以屏蔽編譯器對(duì)構(gòu)造函數(shù)的優(yōu)化操作。

再來(lái)看下運(yùn)行結(jié)果:

ActOne default construct
ActOne copy construct
ActOne destructor
ActOne copy construct
ActOne destructor
ActOne work
++++++++++
ActOne default construct
ActOne copy construct
ActOne destructor
ActOne work
ActOne destructor
ActOne destructor

經(jīng)過(guò)對(duì)比,我們可以發(fā)現(xiàn)未使用右值引用的寫(xiě)法中,拷貝構(gòu)造函數(shù)執(zhí)行了兩次,因?yàn)檫@是make_one()中的return one;會(huì)復(fù)制一次構(gòu)造產(chǎn)生的臨時(shí)對(duì)象,接著在ActOne one = make_one();語(yǔ)句中將臨時(shí)對(duì)象復(fù)制到one變量,這是第二次拷貝構(gòu)造的調(diào)用。

那么,使用了右值引用的方法中,拷貝構(gòu)造函數(shù)只調(diào)用了一次,one2實(shí)際上指向的是一個(gè)臨時(shí)存儲(chǔ)的變量。因?yàn)檫@個(gè)臨時(shí)變量被one2作為右值所引用,因此其生命期也延長(zhǎng)到main函數(shù)結(jié)束才調(diào)用解析構(gòu)造方法。

大家可以好好體會(huì)一下右值引用的作用,對(duì)于性能敏感的C++程序員來(lái)說(shuō),它不僅是降低了程序運(yùn)行的開(kāi)銷,而且臨時(shí)局部變量的可引用,也意味著可以減少動(dòng)態(tài)分配內(nèi)存所帶來(lái)的管理復(fù)雜度。

移動(dòng)語(yǔ)義

可能有同學(xué)出于對(duì)技術(shù)的追求,會(huì)繼續(xù)提問(wèn):那我還想優(yōu)化程序性能,再減少一次拷貝構(gòu)造函數(shù)的開(kāi)銷行不行?應(yīng)當(dāng)對(duì)這樣的提問(wèn)給予積極的回應(yīng),答案是可以的,這就是C++11標(biāo)準(zhǔn)所引入的移動(dòng)語(yǔ)義。

讓我們將上一節(jié)的代碼稍加改動(dòng),然后來(lái)體會(huì)一下移動(dòng)語(yǔ)義的使用。main函數(shù)和make_one函數(shù)沒(méi)有變化,所以僅列出ActOne類的源碼。

class ActOne {
    public:
        ActOne():data_ptr(new uint8_t[DataSize]) { cout << "ActOne default construct" << endl; }
        ActOne(const ActOne &one) { cout << "ActOne copy construct" << endl; }
        ActOne(ActOne &&one) { // 移動(dòng)構(gòu)造方法
            cout << "ActOne move construct" << endl;
            data_ptr = one.data_ptr;
            one.data_ptr = nullptr;
        }
        ~ActOne() {
            cout << "ActOne destructor" << endl;
            if (data_ptr != nullptr) {
                delete []data_ptr;
            }
        }
        void DoSomething() { cout << "ActOne work" << endl; }
    private:
        uint8_t *data_ptr;
};

我想對(duì)于任何一名寫(xiě)C/C++的代碼的程序員來(lái)說(shuō),最大的愿望就是動(dòng)態(tài)內(nèi)存的分配和釋放次數(shù)越少越好。源碼中的ActOne(ActOne &&one)就是一個(gè)移動(dòng)構(gòu)造方法,它接受的是一個(gè)右值作為參數(shù),通過(guò)轉(zhuǎn)移實(shí)參對(duì)象的數(shù)據(jù)以實(shí)現(xiàn)構(gòu)造目標(biāo)對(duì)象。如果是復(fù)制構(gòu)造要怎么做?那就要先為data_ptr分配好內(nèi)存,然后再調(diào)用內(nèi)存拷貝函數(shù)memcpy進(jìn)行一次DataSize字節(jié)數(shù)的復(fù)制。

相比于復(fù)制構(gòu)造方法,移動(dòng)構(gòu)造只需要進(jìn)行指針值的替換即可,其時(shí)空消耗是不可同日而語(yǔ)的。程序添加了一個(gè)移動(dòng)構(gòu)造方法運(yùn)行之后的結(jié)果如下:

ActOne default construct
ActOne move construct
ActOne destructor
ActOne move construct
ActOne destructor
ActOne work
++++++++++
ActOne default construct
ActOne move construct
ActOne destructor
ActOne work
ActOne destructor
ActOne destructor

從上面的結(jié)果可以觀察到,在右值引用和移動(dòng)語(yǔ)義的配合下,內(nèi)存的分配實(shí)際只發(fā)生了一次,移動(dòng)構(gòu)造也只有一次。大家可以往上翻到上一節(jié)的程序打印結(jié)果,對(duì)比一下純拷貝式的構(gòu)造,進(jìn)行了三次內(nèi)存的分配,兩次內(nèi)存深復(fù)制操作。這對(duì)于程序性能的影響已經(jīng)不用多說(shuō)了,各位可以進(jìn)行benchmark測(cè)試以驗(yàn)證移動(dòng)語(yǔ)義帶來(lái)的提升了。

從構(gòu)造函數(shù)的優(yōu)先級(jí)來(lái)說(shuō),編譯器對(duì)于右值會(huì)優(yōu)先使用移動(dòng)構(gòu)造函數(shù)去生成目標(biāo)對(duì)象,如果移動(dòng)構(gòu)造函數(shù)不存在,則是使用復(fù)制構(gòu)造函數(shù)。那么賦值運(yùn)算符能不能進(jìn)行移動(dòng)操作呢?答案是可以的,這個(gè)實(shí)現(xiàn)就留給各位自己去嘗試吧。

提示一下,賦值運(yùn)算符函數(shù)的聲明:

ActOne & operator=(ActOne &&one) {……}

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

我們?cè)賮?lái)學(xué)習(xí)C++11中的一個(gè)新特性,就是萬(wàn)能引用。何謂萬(wàn)能,這個(gè)名稱很唬人,其實(shí)就是一種引用的實(shí)現(xiàn)方法,它既可以引用左值,也可以引用右值。不廢話,還是直接上代碼。

int get_param() { return 100;}
int &&a = get_param(); // a為右值引用
auto &&b = get_param(); // b為萬(wàn)能引用

可以看到,a和b的區(qū)別就在于b的類型是由auto推導(dǎo)而來(lái),而a則是確定類型的。這是作為函數(shù)返回值的,再看一個(gè)模板參數(shù)的例子:

template <class T> 
void func1(T &&t){} // t為萬(wàn)能引用
int a = 100;
const int b = 200;
func1(a);
func1(b);
func1(get_param());

模板方法的參數(shù)t可以接受任何類型的數(shù)據(jù),并推導(dǎo)出一個(gè)引用類型結(jié)果,是什么結(jié)果我們后面會(huì)說(shuō)。所以我們會(huì)發(fā)現(xiàn),萬(wàn)能引用本質(zhì)上是發(fā)生了類型推導(dǎo)。auto &&T &&在初始化過(guò)程中都會(huì)發(fā)生類型推導(dǎo)。

那么推導(dǎo)結(jié)果的規(guī)則也很簡(jiǎn)單:

  1. 如果源對(duì)象是左值,則目標(biāo)對(duì)象會(huì)被推導(dǎo)為左值引用;
  2. 如果源對(duì)象是右值,則目標(biāo)對(duì)象會(huì)被推導(dǎo)為右值引用。

萬(wàn)能引用的概念大家已經(jīng)了解,那么它的用途是什么呢?這就是本節(jié)標(biāo)題所要說(shuō)的完美轉(zhuǎn)發(fā)。實(shí)話說(shuō),我不太喜歡C++術(shù)語(yǔ)中的某些翻譯,在中文語(yǔ)境下很容易讓人費(fèi)解、誤解或是產(chǎn)生不必要的期待。例如C++的萬(wàn)能引用可以實(shí)現(xiàn)完美轉(zhuǎn)發(fā),如果你向一名初學(xué)者來(lái)上這么一句,他是不是會(huì)覺(jué)得“這門語(yǔ)言也太牛X了吧,竟然有萬(wàn)能和完美的特性?” 竊以為換成“全值引用”和“任意轉(zhuǎn)發(fā)”會(huì)不會(huì)低調(diào)和貼切一些呢。

讓我們先從轉(zhuǎn)發(fā)的一個(gè)局限性示例說(shuō)起:

template<class T>
void show_info(T t) {
    cout << "type is: " << typeid(t).name() << endl;
}
template<class T>
void transform(T t) {
    show_info(t);
}
int main() {
    string tmp("test for forward");
    transform(tmp);
}

上述代碼可以工作,但從性能上說(shuō)string類對(duì)象作為參數(shù)傳遞時(shí)會(huì)發(fā)生一次臨時(shí)對(duì)象復(fù)制。在實(shí)際工作中,它可能就是一個(gè)包含有大塊內(nèi)存變量的對(duì)象,顯然不能這么干。那就給參數(shù)加上一個(gè)&符使之成為左值引用吧。下一個(gè)問(wèn)題又來(lái)了,如果傳的參數(shù)是個(gè)右值怎么?看到這里,大家就明白了,要想結(jié)束抬杠在這兒用上萬(wàn)能引用就好了。

最終版完美引用實(shí)現(xiàn),僅列出有變動(dòng)的代碼:

template<class T>
void transform(T &&t) {
    show_info(std::forward<T>(t));
}

std::forward()是標(biāo)準(zhǔn)庫(kù)中的模板方法,它的功能就是可以根據(jù)值的類型將其按左值引用或右值引用進(jìn)行轉(zhuǎn)發(fā)。這樣,既避免了臨時(shí)對(duì)象復(fù)制的開(kāi)銷,又可以支持任意類型的對(duì)象轉(zhuǎn)發(fā)。某種意義上,將其稱為“完美”似乎也并不為過(guò)。畢竟要讓挑剔的C++程序員感到滿意并不容易啊。

需要注意的是,標(biāo)準(zhǔn)庫(kù)中的std::move()方法是將任意實(shí)參轉(zhuǎn)換為右值引用,使用這個(gè)方法不需要指定模板實(shí)參。而std::forward()方法在使用的時(shí)候必須指定模板實(shí)參,也只有它才能按實(shí)際類型進(jìn)行轉(zhuǎn)發(fā)。

結(jié)語(yǔ)

右值引用說(shuō)到這里,相信大家已經(jīng)從一知半解的狀態(tài)到可以理解并運(yùn)用了。它對(duì)于苛求性能以及強(qiáng)調(diào)效率的場(chǎng)景有著非凡的意義,例如在基礎(chǔ)庫(kù)組件的實(shí)現(xiàn)中。雖然大多數(shù)程序員都不一定會(huì)參與到基礎(chǔ)庫(kù)的開(kāi)發(fā)中,但這就看個(gè)人對(duì)于技術(shù)之道的追求了。即使是調(diào)用別人做好的庫(kù)來(lái)組裝一個(gè)應(yīng)用,也會(huì)遇到性能調(diào)優(yōu)的問(wèn)題,那個(gè)時(shí)候你對(duì)老板有多大的價(jià)值就體現(xiàn)在這里了。

如果大家在工作中發(fā)現(xiàn)以前的代碼在用支持C++11的編譯器重新編譯之后,運(yùn)行效率居然有了提升,不用奇怪,這就是基于C++11的新特性做的編譯期優(yōu)化。例如今天學(xué)習(xí)的右值引用、移動(dòng)語(yǔ)義、萬(wàn)能引用、完美轉(zhuǎn)發(fā)等就在語(yǔ)法層面提供了良好的支持。

希望我們接下來(lái)在實(shí)踐中不斷練習(xí),能夠發(fā)揮出C++的最大威力來(lái)!

到此這篇關(guān)于C++精要分析右值引用與完美轉(zhuǎn)發(fā)的應(yīng)用的文章就介紹到這了,更多相關(guān)C++右值引用與完美轉(zhuǎn)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • OpenCV圖像幾何變換之透視變換

    OpenCV圖像幾何變換之透視變換

    這篇文章主要為大家詳細(xì)介紹了OpenCV圖像幾何變換之透視變換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • 解析之C++的列表初始化語(yǔ)法

    解析之C++的列表初始化語(yǔ)法

    有朋友在使用std::array時(shí)發(fā)現(xiàn)一個(gè)奇怪的問(wèn)題:當(dāng)元素類型是復(fù)合類型時(shí),編譯通不過(guò)。按說(shuō)std::array和原生數(shù)組的行為幾乎是一樣的,可為什么當(dāng)元素類型不同時(shí),初始化語(yǔ)法還會(huì)有差別?這篇文章會(huì)介紹這個(gè)問(wèn)題的原理,以及正確的解決方式。
    2021-05-05
  • C++ 多重繼承和虛擬繼承對(duì)象模型、效率分析

    C++ 多重繼承和虛擬繼承對(duì)象模型、效率分析

    本文簡(jiǎn)單介紹多態(tài)和多重繼承、虛擬繼承的基本概念。隨后重點(diǎn)分析了C++中對(duì)象模型之間的差異和運(yùn)行效率
    2014-08-08
  • c++入門必學(xué)庫(kù)函數(shù)sort的基本用法

    c++入門必學(xué)庫(kù)函數(shù)sort的基本用法

    Sort函數(shù)包含在頭文件為#include<algorithm>的c++標(biāo)準(zhǔn)庫(kù)中,調(diào)用標(biāo)準(zhǔn)庫(kù)里的排序方法可以不必知道其內(nèi)部是如何實(shí)現(xiàn)的,只要出現(xiàn)我們想要的結(jié)果即可,下面這篇文章主要給大家介紹了關(guān)于c++入門必學(xué)庫(kù)函數(shù)sort的基本用法,需要的朋友可以參考下
    2022-11-11
  • C語(yǔ)言strlen和sizeof在數(shù)組中的使用詳解

    C語(yǔ)言strlen和sizeof在數(shù)組中的使用詳解

    對(duì)于 strlen 和 sizeof,相信不少程序員會(huì)混淆其功能。雖然從表面上看它們都可以求字符串的長(zhǎng)度,但二者卻存在著許多不同之處及本質(zhì)區(qū)別
    2021-10-10
  • opencv3機(jī)器學(xué)習(xí)之EM算法示例詳解

    opencv3機(jī)器學(xué)習(xí)之EM算法示例詳解

    這篇文章主要介紹了opencv3機(jī)器學(xué)習(xí)之EM算法的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 詳解C語(yǔ)言之順序表

    詳解C語(yǔ)言之順序表

    這篇文章主要為大家介紹了C語(yǔ)言的順序表,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2021-11-11
  • 使用C++實(shí)現(xiàn)Range序列生成器的示例代碼

    使用C++實(shí)現(xiàn)Range序列生成器的示例代碼

    在C++編程中,經(jīng)常需要迭代一系列數(shù)字或其他可迭代對(duì)象,本文將使用C++來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Range封裝,文中的示例代碼講解詳細(xì),感興趣的可以了解下
    2023-11-11
  • C++11新特性之列表初始化的具體使用

    C++11新特性之列表初始化的具體使用

    在我們實(shí)際編程中,我們經(jīng)常會(huì)碰到變量初始化的問(wèn)題,本文主要介紹了C++11新特性之列表初始化的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • C++實(shí)現(xiàn)Go的defer功能(示例代碼)

    C++實(shí)現(xiàn)Go的defer功能(示例代碼)

    defer和go一樣都是Go語(yǔ)言提供的關(guān)鍵字。defer用于資源的釋放,會(huì)在函數(shù)返回之前進(jìn)行調(diào)用。接下來(lái)通過(guò)本文給大家介紹C++實(shí)現(xiàn)Go的defer功能,感興趣的朋友跟隨小編一起看看吧
    2021-07-07

最新評(píng)論