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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

c/c++

C++11 标准新特性: 右值引用与转移语义

發(fā)布時(shí)間:2023/12/10 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++11 标准新特性: 右值引用与转移语义 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文地址

http://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/


C++ 的新標(biāo)準(zhǔn) C++11 已經(jīng)發(fā)布一段時(shí)間了。本文介紹了新標(biāo)準(zhǔn)中的一個(gè)特性,右值引用和轉(zhuǎn)移語(yǔ)義。這個(gè)特性能夠使代碼更加簡(jiǎn)潔高效。


新特性的目的

右值引用 (Rvalue Referene) 是 C++ 新標(biāo)準(zhǔn) (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它實(shí)現(xiàn)了轉(zhuǎn)移語(yǔ)義 (Move Sementics) 和精確傳遞 (Perfect Forwarding)。它的主要目的有兩個(gè)方面:

  • 消除兩個(gè)對(duì)象交互時(shí)不必要的對(duì)象拷貝,節(jié)省運(yùn)算存儲(chǔ)資源,提高效率。
  • 能夠更簡(jiǎn)潔明確地定義泛型函數(shù)。
  • 左值與右值的定義

    C++( 包括 C) 中所有的表達(dá)式和變量要么是左值,要么是右值。通俗的左值的定義就是非臨時(shí)對(duì)象,那些可以在多條語(yǔ)句中使用的對(duì)象。所有的變量都滿足這個(gè)定義,在多條代碼中都可以使用,都是左值。右值是指臨時(shí)的對(duì)象,它們只在當(dāng)前的語(yǔ)句中有效。請(qǐng)看下列示例 :

  • 簡(jiǎn)單的賦值語(yǔ)句 如:int i = 0;

    在這條語(yǔ)句中,i 是左值,0 是臨時(shí)值,就是右值。在下面的代碼中,i 可以被引用,0 就不可以了。立即數(shù)都是右值。

  • 右值也可以出現(xiàn)在賦值表達(dá)式的左邊,但是不能作為賦值的對(duì)象,因?yàn)橛抑抵辉诋?dāng)前語(yǔ)句有效,賦值沒(méi)有意義。

    如:((i>0) ? i : j) = 1;

    在這個(gè)例子中,0 作為右值出現(xiàn)在了”=”的左邊。但是賦值對(duì)象是 i 或者 j,都是左值。

    在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用綁定一個(gè)右值,如 :

    const int &a = 1;

    在這種情況下,右值不能被修改的。但是實(shí)際上右值是可以被修改的,如 :

    T().set().get();

    T 是一個(gè)類,set 是一個(gè)函數(shù)為 T 中的一個(gè)變量賦值,get 用來(lái)取出這個(gè)變量的值。在這句中,T() 生成一個(gè)臨時(shí)對(duì)象,就是右值,set() 修改了變量的值,也就修改了這個(gè)右值。

    既然右值可以被修改,那么就可以實(shí)現(xiàn)右值引用。右值引用能夠方便地解決實(shí)際工程中的問(wèn)題,實(shí)現(xiàn)非常有吸引力的解決方案。

  • 左值和右值的語(yǔ)法符號(hào)

    左值的聲明符號(hào)為”&”, 為了和左值區(qū)分,右值的聲明符號(hào)為”&&”。

    示例程序 :

    void process_value(int& i) { std::cout << "LValue processed: " << i << std::endl; } void process_value(int&& i) { std::cout << "RValue processed: " << i << std::endl; } int main() { int a = 0; process_value(a); process_value(1); }

    運(yùn)行結(jié)果 :

    LValue processed: 0 RValue processed: 1

    Process_value 函數(shù)被重載,分別接受左值和右值。由輸出結(jié)果可以看出,臨時(shí)對(duì)象是作為右值處理的。

    但是如果臨時(shí)對(duì)象通過(guò)一個(gè)接受右值的函數(shù)傳遞給另一個(gè)函數(shù)時(shí),就會(huì)變成左值,因?yàn)檫@個(gè)臨時(shí)對(duì)象在傳遞過(guò)程中,變成了命名對(duì)象。

    示例程序 :

    void process_value(int& i) { std::cout << "LValue processed: " << i << std::endl; } void process_value(int&& i) { std::cout << "RValue processed: " << i << std::endl; } void forward_value(int&& i) { process_value(i); } int main() { int a = 0; process_value(a); process_value(1); forward_value(2); }

    運(yùn)行結(jié)果 :

    LValue processed: 0 RValue processed: 1 LValue processed: 2

    雖然 2 這個(gè)立即數(shù)在函數(shù) forward_value 接收時(shí)是右值,但到了 process_value 接收時(shí),變成了左值。

    轉(zhuǎn)移語(yǔ)義的定義

    右值引用是用來(lái)支持轉(zhuǎn)移語(yǔ)義的。轉(zhuǎn)移語(yǔ)義可以將資源 ( 堆,系統(tǒng)對(duì)象等 ) 從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,這樣能夠減少不必要的臨時(shí)對(duì)象的創(chuàng)建、拷貝以及銷毀,能夠大幅度提高 C++ 應(yīng)用程序的性能。臨時(shí)對(duì)象的維護(hù) ( 創(chuàng)建和銷毀 ) 對(duì)性能有嚴(yán)重影響。

    轉(zhuǎn)移語(yǔ)義是和拷貝語(yǔ)義相對(duì)的,可以類比文件的剪切與拷貝,當(dāng)我們將文件從一個(gè)目錄拷貝到另一個(gè)目錄時(shí),速度比剪切慢很多。

    通過(guò)轉(zhuǎn)移語(yǔ)義,臨時(shí)對(duì)象中的資源能夠轉(zhuǎn)移其它的對(duì)象里。

    在現(xiàn)有的 C++ 機(jī)制中,我們可以定義拷貝構(gòu)造函數(shù)和賦值函數(shù)。要實(shí)現(xiàn)轉(zhuǎn)移語(yǔ)義,需要定義轉(zhuǎn)移構(gòu)造函數(shù),還可以定義轉(zhuǎn)移賦值操作符。對(duì)于右值的拷貝和賦值會(huì)調(diào)用轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值操作符。如果轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移拷貝操作符沒(méi)有定義,那么就遵循現(xiàn)有的機(jī)制,拷貝構(gòu)造函數(shù)和賦值操作符會(huì)被調(diào)用。

    普通的函數(shù)和操作符也可以利用右值引用操作符實(shí)現(xiàn)轉(zhuǎn)移語(yǔ)義。

    實(shí)現(xiàn)轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值函數(shù)

    以一個(gè)簡(jiǎn)單的 string 類為示例,實(shí)現(xiàn)拷貝構(gòu)造函數(shù)和拷貝賦值操作符。

    示例程序 :

    class MyString { private: char* _data; size_t _len; void _init_data(const char *s) { _data = new char[_len+1]; memcpy(_data, s, _len); _data[_len] = '\0'; } public: MyString() { _data = NULL; _len = 0; } MyString(const char* p) { _len = strlen (p); _init_data(p); } MyString(const MyString& str) { _len = str._len; _init_data(str._data); std::cout << "Copy Constructor is called! source: " << str._data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { _len = str._len; _init_data(str._data); } std::cout << "Copy Assignment is called! source: " << str._data << std::endl; return *this; } virtual ~MyString() { if (_data) free(_data); } }; int main() { MyString a; a = MyString("Hello"); std::vector<MyString> vec; vec.push_back(MyString("World")); }

    運(yùn)行結(jié)果 :

    Copy Assignment is called! source: Hello Copy Constructor is called! source: World

    這個(gè) string 類已經(jīng)基本滿足我們演示的需要。在 main 函數(shù)中,實(shí)現(xiàn)了調(diào)用拷貝構(gòu)造函數(shù)的操作和拷貝賦值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是臨時(shí)對(duì)象,也就是右值。雖然它們是臨時(shí)的,但程序仍然調(diào)用了拷貝構(gòu)造和拷貝賦值,造成了沒(méi)有意義的資源申請(qǐng)和釋放的操作。如果能夠直接使用臨時(shí)對(duì)象已經(jīng)申請(qǐng)的資源,既能節(jié)省資源,有能節(jié)省資源申請(qǐng)和釋放的時(shí)間。這正是定義轉(zhuǎn)移語(yǔ)義的目的。

    我們先定義轉(zhuǎn)移構(gòu)造函數(shù)。

    MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str._data << std::endl; _len = str._len; _data = str._data; str._len = 0; str._data = NULL; }

    和拷貝構(gòu)造函數(shù)類似,有幾點(diǎn)需要注意:

    1. 參數(shù)(右值)的符號(hào)必須是右值引用符號(hào),即“&&”。

    2. 參數(shù)(右值)不可以是常量,因?yàn)槲覀冃枰薷挠抑怠?/p>

    3. 參數(shù)(右值)的資源鏈接和標(biāo)記必須修改。否則,右值的析構(gòu)函數(shù)就會(huì)釋放資源。轉(zhuǎn)移到新對(duì)象的資源也就無(wú)效了。

    現(xiàn)在我們定義轉(zhuǎn)移賦值操作符。

    MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str._data << std::endl; if (this != &str) { _len = str._len; _data = str._data; str._len = 0; str._data = NULL; } return *this; }

    這里需要注意的問(wèn)題和轉(zhuǎn)移構(gòu)造函數(shù)是一樣的。

    增加了轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移復(fù)制操作符后,我們的程序運(yùn)行結(jié)果為 :

    Move Assignment is called! source: Hello Move Constructor is called! source: World

    由此看出,編譯器區(qū)分了左值和右值,對(duì)右值調(diào)用了轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值操作符。節(jié)省了資源,提高了程序運(yùn)行的效率。

    有了右值引用和轉(zhuǎn)移語(yǔ)義,我們?cè)谠O(shè)計(jì)和實(shí)現(xiàn)類時(shí),對(duì)于需要?jiǎng)討B(tài)申請(qǐng)大量資源的類,應(yīng)該設(shè)計(jì)轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值函數(shù),以提高應(yīng)用程序的效率。

    標(biāo)準(zhǔn)庫(kù)函數(shù) std::move

    既然編譯器只對(duì)右值引用才能調(diào)用轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值函數(shù),而所有命名對(duì)象都只能是左值引用,如果已知一個(gè)命名對(duì)象不再被使用而想對(duì)它調(diào)用轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移賦值函數(shù),也就是把一個(gè)左值引用當(dāng)做右值引用來(lái)使用,怎么做呢?標(biāo)準(zhǔn)庫(kù)提供了函數(shù) std::move,這個(gè)函數(shù)以非常簡(jiǎn)單的方式將左值引用轉(zhuǎn)換為右值引用。

    示例程序 :

    void ProcessValue(int& i) { std::cout << "LValue processed: " << i << std::endl; } void ProcessValue(int&& i) { std::cout << "RValue processed: " << i << std::endl; } int main() { int a = 0; ProcessValue(a); ProcessValue(std::move(a)); }

    運(yùn)行結(jié)果 :

    LValue processed: 0 RValue processed: 0

    std::move在提高 swap 函數(shù)的的性能上非常有幫助,一般來(lái)說(shuō),swap函數(shù)的通用定義如下:

    template <class T> swap(T& a, T& b) { T tmp(a); // copy a to tmp a = b; // copy b to a b = tmp; // copy tmp to b }

    有了 std::move,swap 函數(shù)的定義變?yōu)?:

    template <class T> swap(T& a, T& b) { T tmp(std::move(a)); // move a to tmp a = std::move(b); // move b to a b = std::move(tmp); // move tmp to b }

    通過(guò) std::move,一個(gè)簡(jiǎn)單的 swap 函數(shù)就避免了 3 次不必要的拷貝操作。

    精確傳遞 (Perfect Forwarding)

    本文采用精確傳遞表達(dá)這個(gè)意思。”P(pán)erfect Forwarding”也被翻譯成完美轉(zhuǎn)發(fā),精準(zhǔn)轉(zhuǎn)發(fā)等,說(shuō)的都是一個(gè)意思。

    精確傳遞適用于這樣的場(chǎng)景:需要將一組參數(shù)原封不動(dòng)的傳遞給另一個(gè)函數(shù)。

    “原封不動(dòng)”不僅僅是參數(shù)的值不變,在 C++ 中,除了參數(shù)值之外,還有一下兩組屬性:

    左值/右值和 const/non-const。 精確傳遞就是在參數(shù)傳遞過(guò)程中,所有這些屬性和參數(shù)值都不能改變。在泛型函數(shù)中,這樣的需求非常普遍。

    下面舉例說(shuō)明。函數(shù) forward_value 是一個(gè)泛型函數(shù),它將一個(gè)參數(shù)傳遞給另一個(gè)函數(shù) process_value。

    forward_value 的定義為:

    template <typename T> void forward_value(const T& val) { process_value(val); } template <typename T> void forward_value(T& val) { process_value(val); }

    函數(shù) forward_value 為每一個(gè)參數(shù)必須重載兩種類型,T& 和 const T&,否則,下面四種不同類型參數(shù)的調(diào)用中就不能同時(shí)滿足 ?:

    int a = 0; const int &b = 1; forward_value(a); // int& forward_value(b); // const int& forward_value(2); // int&

    對(duì)于一個(gè)參數(shù)就要重載兩次,也就是函數(shù)重載的次數(shù)和參數(shù)的個(gè)數(shù)是一個(gè)正比的關(guān)系。這個(gè)函數(shù)的定義次數(shù)對(duì)于程序員來(lái)說(shuō),是非常低效的。我們看看右值引用如何幫助我們解決這個(gè)問(wèn)題 ?:

    template <typename T> void forward_value(T&& val) { process_value(val); }

    只需要定義一次,接受一個(gè)右值引用的參數(shù),就能夠?qū)⑺械膮?shù)類型原封不動(dòng)的傳遞給目標(biāo)函數(shù)。四種不用類型參數(shù)的調(diào)用都能滿足,參數(shù)的左右值屬性和 const/non-cosnt 屬性完全傳遞給目標(biāo)函數(shù) process_value。這個(gè)解決方案不是簡(jiǎn)潔優(yōu)雅嗎?

    int a = 0; const int &b = 1; forward_value(a); // int& forward_value(b); // const int& forward_value(2); // int&&

    C++11 中定義的 T&& 的推導(dǎo)規(guī)則為:

    右值實(shí)參為右值引用,左值實(shí)參仍然為左值引用。

    一句話,就是參數(shù)的屬性不變。這樣也就完美的實(shí)現(xiàn)了參數(shù)的完整傳遞。

    右值引用,表面上看只是增加了一個(gè)引用符號(hào),但它對(duì) C++ 軟件設(shè)計(jì)和類庫(kù)的設(shè)計(jì)有非常大的影響。它既能簡(jiǎn)化代碼,又能提高程序運(yùn)行效率。每一個(gè) C++ 軟件設(shè)計(jì)師和程序員都應(yīng)該理解并能夠應(yīng)用它。我們?cè)谠O(shè)計(jì)類的時(shí)候如果有動(dòng)態(tài)申請(qǐng)的資源,也應(yīng)該設(shè)計(jì)轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移拷貝函數(shù)。在設(shè)計(jì)類庫(kù)時(shí),還應(yīng)該考慮 std::move 的使用場(chǎng)景并積極使用它。

    總結(jié)

    右值引用和轉(zhuǎn)移語(yǔ)義是 C++ 新標(biāo)準(zhǔn)中的一個(gè)重要特性。每一個(gè)專業(yè)的 C++ 開(kāi)發(fā)人員都應(yīng)該掌握并應(yīng)用到實(shí)際項(xiàng)目中。在有機(jī)會(huì)重構(gòu)代碼時(shí),也應(yīng)該思考是否可以應(yīng)用新也行。在使用之前,需要檢查一下編譯器的支持情況。


    總結(jié)

    以上是生活随笔為你收集整理的C++11 标准新特性: 右值引用与转移语义的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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