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

歡迎訪問 生活随笔!

生活随笔

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

c/c++

C++11新特性之左值右值及移动语句与完美转发

發(fā)布時(shí)間:2025/3/8 c/c++ 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++11新特性之左值右值及移动语句与完美转发 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  • C左值右值
    • 左值和右值的由來
    • 什么是左值和右值
    • 左值右值的本質(zhì)
    • 引用
      • 左值引用
      • 右值引用
    • 移動(dòng)語句與完美轉(zhuǎn)發(fā)
      • 移動(dòng)語句
        • 實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù)和轉(zhuǎn)移賦值函數(shù)
        • stdmove
        • 完美轉(zhuǎn)發(fā)Perfect Forwarding

C++左值右值

自從C++11發(fā)布之后,出現(xiàn)了一個(gè)新的概念,即左值和右值,英文為lvalue和rvalue,這兩個(gè)是比較晦澀難懂的基礎(chǔ)概念,為什么說是基礎(chǔ)的概念呢?因?yàn)橹挥辛私饬怂?#xff0c;才能真正理解move和forward語句。

左值和右值的由來

左值和右值,最早是從C語言繼承而來的。在C語言中,或者是它的繼承版本中有如下變現(xiàn)形式:

  • 左值是可以位于賦值運(yùn)算符“=”左側(cè)的變量或表達(dá)式,也可以位于賦值運(yùn)算符“=”右側(cè)
  • 右值是不可以位于賦值運(yùn)算符“=”左側(cè)的表達(dá)式,只能出現(xiàn)在等號(hào)右邊的變量或者表達(dá)式

我們來看看例子:

例子1

int a; //聲明變量a int b; //聲明變量ba = 3; //賦值語句,將a的值重新賦值為3,此時(shí)a為左值 b = 4; //此時(shí)b為左值a = b; //此時(shí)a為左值,b為右值 b = a;//此時(shí)b為左值,a為右值3 = a; //編譯錯(cuò)誤,這里應(yīng)該很好理解,3不可能再被賦值了 a + b = 4; //編譯錯(cuò)誤,這里相當(dāng)于7 = 4,也是不合理的

例子2

//定義了兩個(gè)函數(shù)foo1和foo2 int foo1(int number) {return number; }int foo2( int number ) {return number; }main() {foo1(1) = foo2(2); //編譯錯(cuò)誤,因?yàn)閒oo1和foo2的返回值只能作為右值,不能放在等號(hào)的左邊int temp = foo1(1) * foo2(2); //temp為左值,foo1(1) * foo2(2)為右值 }

上面這些例子就是左值和右值的例子。

什么是左值和右值

一個(gè)變量或者表達(dá)式是左值還是右值,取決于我們使用的是它的值還是它在內(nèi)存中的位置(作為實(shí)例的身份)。

int a; //聲明變量a int b; //聲明變量ba = b; //此時(shí)a為左值,b為右值

這個(gè)例子中,將b的值賦值給a,將值保存在a的內(nèi)存中,b在這里面是右值,a在這里面是左值
因?yàn)閎作為實(shí)例既可以當(dāng)做左值也可以當(dāng)做右值。

所以判斷一個(gè)值是左值還是右值要根據(jù)實(shí)際在語句匯總的含義來確定。

總結(jié)第一點(diǎn):

  • 在一般情況下,需要右值的地方可以用左值來代替,需要左值的地方必須使用左值
  • 左值存放在實(shí)例中,有持久的狀態(tài),而右值是字面常量,要么是在表達(dá)式求值過程中創(chuàng)建的臨時(shí)實(shí)例,沒有持久的狀態(tài)

重點(diǎn):

能取得到地址的變量或者表達(dá)式就是左值,反之為右值。

那現(xiàn)在我們來看看下面的例子哪個(gè)是左值,哪個(gè)是右值?

int a = 0; int b = 1; a++; ++b;

我來宣布答案,a++為右值,++b為左值,首先我們先驗(yàn)證一下:

int a = 0; int b = 1; a++ = 5; //error: lvalue required as left operand of assignment ++b = 5;

實(shí)驗(yàn)的結(jié)果也是正確的,那我們來分析一下:
對(duì)于a++
1. a++首先產(chǎn)生一個(gè)臨時(shí)變量,記錄a的值
2. 然后將a+1
3. 接著返回臨時(shí)變量

根據(jù)這個(gè)過程我們知道 int a = 0;int c = a++; 的值應(yīng)該是c為0;而a變?yōu)榱?,
所以a++此時(shí)將臨時(shí)變量返回給了c,那么這個(gè)臨時(shí)變量我們是不能獲取地址的,也就
是使用“&”。所以結(jié)論就是a++為右值。

對(duì)于++b
1. 進(jìn)行了b = b + 1
2. 返回變量b

根據(jù)這個(gè)過程,我們是可以取b的地址的,所以b是左值。

左值右值的本質(zhì)

int a = 5; int c = a + a;

a就是左值,5就是右值。 a + a 表達(dá)式中,a以右值傳入,相加之后也以右值返回。

左值就是對(duì)一塊內(nèi)存區(qū)域的引用(這個(gè)并不是c++11中的int &a 之類的引用),
比如上邊的a,就對(duì)應(yīng)了一塊內(nèi)存區(qū)域(起始地址&a,大小為sizeof(int) )。

專業(yè)的解釋:

An object is a region of storage that can be examined and stored into. An lvalue is an expression that refers to such an object. An lvalue does not necessarily permit modification of the object it designates. For example, a const object is an lvalue that cannot be modified.

對(duì)于每個(gè)變量,都有2個(gè)值與其相關(guān)聯(lián):

  • 數(shù)據(jù)值,存儲(chǔ)在某個(gè)內(nèi)存地址中,也稱為右值,右值是被讀取的值,不可修改。
  • 地址值,即存儲(chǔ)數(shù)據(jù)值的那塊內(nèi)存地址,也稱左值。
  • 所以左值既可以當(dāng)作左值也可以作為右值。就是這么神奇。

    引用

    在C++中,有兩種對(duì)實(shí)例的引用:左值引用和右值引用。

    左值引用

    左值引用是常見的引用,C++中可以使用“&”符號(hào)定義引用,如果一個(gè)左值同時(shí)也是引用,那么就稱其為“左值引用”。如

    std::string str; std::string& strRef = str; // strRef為左值也為引用,稱其為左值引用

    非const左值引用不能使用右值對(duì)其賦值

    std::string& strRef = "abc"; // error: abc字符串為右值,

    假設(shè)上面可以的話,就會(huì)遇到一個(gè)問題:如何修改右值的值?因?yàn)橐檬强梢院罄m(xù)被賦值的。根據(jù)上面的定義,右值連可被獲取的內(nèi)存地址都沒有,也就談不上對(duì)其進(jìn)行賦值。

    但是const左值引用就可以使用右值,因?yàn)槌A坎荒鼙恍薷?#xff0c;也不存在上面糾結(jié)的問題:

    const std::string strRef = "abc";

    再比如,我們經(jīng)常使用左值作為函數(shù)的參數(shù)類型,可以減少不必要的對(duì)象復(fù)制:

    int foo( int& number ) {return number; }main() {int a = foo(1); //錯(cuò)誤int b = 1;int c = foo(b); //通過 }

    我們將上面的int& number改為 const int& number即可。

    補(bǔ)充知識(shí):

    什么是CV限定符(CV-qualified),如果變量聲明時(shí)類型帶有const或者volatile,就說此變量類型具有CV限定符。

    右值引用

    右值引用也是引用,但是它只能且必須綁定在右值上。

    int a = 5; int& b = a; // a綁定在左值引用b上 int&& c = a; // error:a可以是左值,所以不能將它綁定在右值引用上。 int&& d = 30; // 將右值30綁定在右值引用上 int&& e = a * 1 // a * 1的結(jié)果是一個(gè)臨時(shí)對(duì)象,為右值,所以可以綁定在右值引用上

    結(jié)論:

    由于右值引用只能綁定在右值上,而右值要么是字面常量,要么是臨時(shí)對(duì)象,所以:

    右值引用的對(duì)象,是臨時(shí)的,即將被銷毀; 并且右值引用的對(duì)象,不會(huì)在其它地方使用。

    移動(dòng)語句與完美轉(zhuǎn)發(fā)

    這里涉及前面提過的move和forward兩個(gè)函數(shù),即移動(dòng)語句和轉(zhuǎn)發(fā)。

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

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

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

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

    通過移動(dòng)語句,臨時(shí)對(duì)象中的資源能夠轉(zhuǎn)移其他的對(duì)象里。

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

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

    class MyString { public: MyString() { m_data = nullptr; m_len = 0; }private: char* m_data; size_t m_len; void initData(const char *s) { m_data = new char[m_len+1]; memcpy(m_data, s, m_len); m_data[m_len] = '\0'; } MyString(const char* p) { m_len = strlen (p); initData(p); } MyString(const MyString& str) { m_len = str.m_len; initData(str.m_data); std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { m_len = str.m_len; initData(str.m_data); } std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl; return *this; } virtual ~MyString() { } }; int main() { MyString a; a = MyString("Hello"); //調(diào)用拷貝構(gòu)造函數(shù),MyString("Hello")為臨時(shí)對(duì)象,即右值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)造和拷貝賦值,造成了沒有意義的資源申請(qǐng)和釋放的操作。如果能夠直接使用臨時(shí)對(duì)象已經(jīng)申請(qǐng)的資源,既能節(jié)省資源,有能節(jié)省資源申請(qǐng)和釋放的時(shí)間。這正是定義轉(zhuǎn)移語義的目的。

    MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str.m_data << std::endl; m_len = str.m_len; m_data = str.m_data; str.m_len = 0; str.m_data = nullptr; }MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str.m_data << std::endl; if (this != &str) { m_len = str.m_len; m_data = str.m_data; str.m_len = 0; str.m_data = nullptr; } return *this; }增加后的運(yùn)行結(jié)果:Move Assignment is called! source: Hello Move Constructor is called! source: World

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

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

    std::move

    我們先來看一個(gè)例子:

    void processValue( int& value ) {std::cout << "lvalue process: " << value << std::endl; }void processValue( int&& value ) {std::cout << "rvalue process: " << value << std::endl; }main() {int a = 0;processValue(a); //傳入左值processValue(1); //傳如右值 }結(jié)果: lvalue process: 0 rvalue process: 1

    通過這個(gè)例子來看,processValue函數(shù)被重載,分別接受左值和右值。由輸出結(jié)果可以看出,臨時(shí)對(duì)象是作為右值處理的。

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

    void processValue( int& value ) {std::cout << "lvalue process: " << value << std::endl; }void processValue( int&& value ) {std::cout << "rvalue process: " << value << std::endl; }void forwardValue( int&& value ) {processValue(value); }main() {int a = 0;processValue(a);processValue(1);forwardValue(2); }結(jié)果: lvalue process: 0 rvalue process: 1 lvalue process: 2

    我們可以看出最后一個(gè)函數(shù)調(diào)用,2是右值,可以返回的時(shí)候卻變成了左值。這里面我們可以使用std::move(var)將變量轉(zhuǎn)移為右值語句。

    修改為:

    ... void forwardValue( int&& value ) {processValue(std::move(value) ); } ...

    既然編譯器只對(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)做右值引用來使用,怎么做呢?標(biāo)準(zhǔn)庫提供了函數(shù) std::move,這個(gè)函數(shù)以非常簡(jiǎn)單的方式將左值引用轉(zhuǎn)換為右值引用。

    std::move在提高 swap 函數(shù)的的性能上非常有幫助,一般來說,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 }

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

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

    Perfect Forwarding也被翻譯成完美轉(zhuǎn)發(fā),精準(zhǔn)轉(zhuǎn)發(fā)等,說的都是一個(gè)意思。

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

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

    • 左值/右值
    • const/non-const。

    完美轉(zhuǎn)發(fā)就是在參數(shù)傳遞過程中,所有這些屬性和參數(shù)值都不能改變。

    重點(diǎn):完美轉(zhuǎn)發(fā)僅與類模板或函數(shù)模板的環(huán)境有關(guān)。

    示例:

    class Person { public:template<typename T1, typename T2>Person(T1&& first, T2&& second ) : firstname{std::forward<T1>(first)},secondname{std::forward<T2>(second)}{}string getName() const{return firstname.getName() + " " + secondname.getName(); } private:Name firstname;Name secondname; }class Name { public:Name( const string& aName ): name{aName}{cout << "Lvalue Name constructor." << endl;}Name( string&& aName ): name{std::move(aName)}{cout << "Rvalue Name constructor." << endl;}const string& getName() const { return name; } private:string name; }main() {Person me{string{"abc"}, string{"def"}};string first{"lll"};string second{"ggg"};Person other{first, second}; }輸出結(jié)果: Rvalue Name constructor. Rvalue Name constructor. Lvalue Name constructor. Lvalue Name constructor.

    總結(jié)

    以上是生活随笔為你收集整理的C++11新特性之左值右值及移动语句与完美转发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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