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

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

生活随笔

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

c/c++

一文读懂什么是C++移动语义《一》

發(fā)布時(shí)間:2025/3/15 c/c++ 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文读懂什么是C++移动语义《一》 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

C++ 右值引用

block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38

右值的引入

作為在C++11中引入的一個(gè)類型,容易引起誤解的是,右值引用并沒(méi)有說(shuō)明引入是為了什么,是為了解決什么問(wèn)題。

右值引用可以解決以下問(wèn)題

  • 實(shí)現(xiàn)移動(dòng)語(yǔ)義
  • 完美轉(zhuǎn)發(fā)
  • 左值和右值來(lái)自原先的C語(yǔ)言,左值可以出現(xiàn)在賦值左邊或者右邊,而右值只能出現(xiàn)在賦值的右邊

    int a = 42; int b = 43;// a and b are both l-values: a = b; // ok b = a; // ok a = a * b; // ok// a * b is an rvalue: int c = a * b; // ok, rvalue on right hand side of assignment a * b = 42; // error, rvalue on left hand side of assignment

    在 C++ 中,這作為第一個(gè)直觀的左值和右值方法仍然很有用。但是,帶有用戶定義類型的 C++ 引入了一些關(guān)于可修改性和可分配性的微妙之處,導(dǎo)致此定義不正確。我們沒(méi)有必要進(jìn)一步討論這個(gè)問(wèn)題。這是一個(gè)替代定義,盡管它仍然存在爭(zhēng)議,但它將使您能夠處理右值引用:左值是一個(gè)引用內(nèi)存位置的表達(dá)式,并允許我們通過(guò)&操作符取得地址,右值,不是左值的都是右值。

    // lvalues: // int i = 42; i = 43; // ok, i is an lvalue int* p = &i; // ok, i is an lvalue int& foo(); foo() = 42; // ok, foo() is an lvalue int* p1 = &foo(); // ok, foo() is an lvalue// rvalues: // int foobar(); int j = 0; j = foobar(); // ok, foobar() is an rvalue int* p2 = &foobar(); // error, cannot take the address of an rvalue j = 42; // ok, 42 is an rvalue

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

    假設(shè)有一個(gè)類X,類中的成員變量m_pResource是一個(gè)需要花費(fèi)時(shí)間和內(nèi)存取進(jìn)行構(gòu)造和析構(gòu)的類型,比如m_pResource是一個(gè)vector類型,對(duì)其進(jìn)行賦值時(shí)將會(huì)產(chǎn)生大量的析構(gòu)和構(gòu)造函數(shù)的調(diào)用。

    X& X::operator=(X const & rhs) {// [...]// Make a clone of what rhs.m_pResource refers to.// Destruct the resource that m_pResource refers to. // Attach the clone to m_pResource.// [...] }

    同樣的問(wèn)題會(huì)出現(xiàn)在copy構(gòu)造函數(shù)上

    X foo(); X x; // perhaps use x in various ways x = foo();
    • clones the resource from the temporary returned by foo,

    • destructs the resource held by x and replaces it with the clone,

    • destructs the temporary and thereby releases its resource.

    當(dāng)賦值操作符的右邊是右值的話,只是交換值的指針是比較高效的

    // [...] // swap m_pResource and rhs.m_pResource // [...]

    上述這種操作就是移動(dòng)語(yǔ)義,可以通過(guò)操作符重載實(shí)現(xiàn)

    X& X::operator=(<mystery type> rhs) {// [...]// swap this->m_pResource and rhs.m_pResource// [...] }

    以上調(diào)用無(wú)論是賦值還是copy構(gòu)造函數(shù),都會(huì)導(dǎo)致大量的構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用(如當(dāng)vector中存儲(chǔ)很多的類對(duì)象時(shí)),因此我們當(dāng)然希望能夠?qū)崿F(xiàn)對(duì)傳入類型的引用,從而避免這些構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用

    block://6984620384730546178?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38

    右值引用

    如果X是一個(gè)類型,那么X&& 就是對(duì)X類型的右值引用,為了更好的區(qū)分X&被稱為左值引用

    一個(gè)右值引用類型很多地方表現(xiàn)與左值引用相同,除了一些例外。最重要的一條就是,當(dāng)進(jìn)行函數(shù)重載的時(shí)候,左值當(dāng)成參數(shù)傳入函數(shù),偏向調(diào)用左值引用的函數(shù);當(dāng)右值傳入函數(shù)時(shí),更加偏向調(diào)用右值重載的函數(shù)

    void foo(X& x); // 左值函數(shù)重載 void foo(X&& x); // 右值函數(shù)重載X x; X foobar();foo(x); // argument is lvalue: calls foo(X&) foo(foobar()); // argument is rvalue: calls foo(X&&)

    Rvalue references allow a function to branch at compile time (via overload resolution) on the condition “Am I being called on an lvalue or an rvalue?”

    大體意思就是,右值引用允許編譯器期間通過(guò)是右值還是左值調(diào)用不同的函數(shù)

    當(dāng)然你可以使用上述方法重載任何函數(shù),就像上述所示。但是通常會(huì)被用于重載拷貝構(gòu)造函數(shù)和賦值構(gòu)造函數(shù),用來(lái)實(shí)現(xiàn)移動(dòng)語(yǔ)義

    X& X::operator=(X const & rhs); // classical implementationX& X::operator=(X&& rhs) {// Move semantics: exchange content between this and rhsreturn *this; } Note: If you implement void foo(X&); but not void foo(X&&); then of course the behavior is unchanged: foo can be called on l-values, but not on r-values. If you implement void foo(X const &); but not void foo(X&&); then again, the behavior is unchanged: foo can be called on l-values and r-values, but it is not possible to make it distinguish between l-values and r-values. That is possible only by implementing void foo(X&&); as well. Finally, if you implement void foo(X&&); but neither one of void foo(X&); and void foo(X const &); then, according to the final version of C++11, foo can be called on r-values, but trying to call it on an l-value will trigger a compile error.

    強(qiáng)制移動(dòng)語(yǔ)義

    我們都知道,在給予更多控制權(quán)和避免粗心大意犯錯(cuò)方面C++選擇給予更多的控制權(quán),你不但可以在右值上實(shí)現(xiàn)移動(dòng)語(yǔ)義,而且你可以自行決定在左值上實(shí)現(xiàn)移動(dòng)語(yǔ)義,一個(gè)很好的例子就是std::swap函數(shù)

    template<class T> void swap(T& a, T& b) { T tmp(a);a = b; b = tmp; } X a, b; swap(a, b);

    這里沒(méi)有使用右值,因此有沒(méi)有實(shí)現(xiàn)移動(dòng)語(yǔ)義,但是我們知道實(shí)現(xiàn)移動(dòng)語(yǔ)義會(huì)更好,只要變量作為復(fù)制構(gòu)造或者賦值的源出現(xiàn),該變量要么根本就不再使用,要么就作為賦值的目標(biāo)。

    C++11中與一個(gè)被調(diào)用的庫(kù)函數(shù)std::move可以將其參數(shù)轉(zhuǎn)換 右值, 不做其他事情

    void swap(T& a, T& b) { T tmp(std::move(a));a = std::move(b); b = std::move(tmp); } X a, b; swap(a, b);

    修改之后上述三行實(shí)現(xiàn)了移動(dòng)語(yǔ)義,需要注意的是,對(duì)于那些沒(méi)有實(shí)現(xiàn)移動(dòng)語(yǔ)義的類型(即:沒(méi)有使用右值引用版本重載它們的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符),對(duì)于這些類型新的swap就和舊的一樣

    既然、知道了移動(dòng)語(yǔ)義std::move,如下:

    a = b;

    你期望在這里發(fā)生什么?你期望a持有的對(duì)象被b的復(fù)制出來(lái)的副本替換,并且希望a先前持有的對(duì)象析構(gòu),現(xiàn)在我們考慮一下語(yǔ)義:

    a = std::move(b);

    如果實(shí)現(xiàn)了移動(dòng)語(yǔ)義,會(huì)交換a和b持有的對(duì)象,不會(huì)有任何對(duì)象進(jìn)行析構(gòu)。當(dāng)然結(jié)束之后a原先持有的對(duì)象的生命周期將和b的作用范圍綁定,b超出范圍a原先持有的對(duì)象將會(huì)被銷毀。

    所以從某種意義上說(shuō),我們?cè)谶@里陷入了非確定性破壞的陰暗世界:一個(gè)變量已被分配,但該變量以前持有的對(duì)象仍在某處。只要該對(duì)象的銷毀不會(huì)產(chǎn)生任何外界可見(jiàn)的副作用,就可以了。但有時(shí)析構(gòu)函數(shù)確實(shí)有這樣的副作用。一個(gè)例子是釋放析構(gòu)函數(shù)內(nèi)的鎖。因此,具有副作用的對(duì)象銷毀的任何部分都應(yīng)該在復(fù)制賦值運(yùn)算符的右值引用重載中顯式執(zhí)行:

    X& X::operator=(X&& rhs) {// Perform a cleanup that takes care of at least those parts of the// destructor that have side effects. Be sure to leave the object// in a destructible and assignable state.// Move semantics: exchange content between this and rhsreturn *this; }

    右值引用就是右值嗎?

    像以前一樣,我們?yōu)閄實(shí)現(xiàn)復(fù)制構(gòu)造函數(shù)和賦值操作符重載來(lái)實(shí)現(xiàn)移動(dòng)語(yǔ)義。

    假如:

    void foo(X&& x) {X anotherX = x; // 調(diào)用右值引用賦值重載函數(shù)還是左值???// ... }

    代碼中函數(shù)內(nèi)x是一個(gè)左值引用,然而我們期望讓右值引用就是本身就是右值。右值引用的設(shè)計(jì)者提供了一個(gè)更好的思路:

    Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.

    大意就是,右值引用可以是左值也可以是右值,評(píng)判的標(biāo)準(zhǔn)是,如果這個(gè)值有命名就是左值,如果沒(méi)有就是右值。

    那么上述代碼中,雖然參數(shù)傳進(jìn)的是右值,但是進(jìn)入函數(shù)的時(shí)候,因?yàn)閤已經(jīng)有命名了,所以函數(shù)內(nèi)部的x是左值,那么函數(shù)內(nèi)部調(diào)用的也是左值的賦值函數(shù)

    void foo(X&& x) {X anotherX = x; // calls X(X const & rhs) }

    如下是一個(gè)沒(méi)有名字的右值,因此會(huì)調(diào)用右值賦值函數(shù)

    X&& goo(); X x = goo(); // calls X(X&& rhs) because the thing on// the right hand side has no name

    這種設(shè)計(jì)的背后思路就是:允許移動(dòng)語(yǔ)義應(yīng)用于一些有名字的對(duì)象

    X anotherX = x;// x is still in scope!

    以上語(yǔ)句是非常危險(xiǎn)的,移動(dòng)的食物應(yīng)該在移動(dòng)后立即死亡并消失,因此有一條規(guī)則,如果它有一個(gè)名字,那么它就是左值

    全文鏈接:右值引用

    總結(jié)

    以上是生活随笔為你收集整理的一文读懂什么是C++移动语义《一》的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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