一文读懂什么是C++移动语义《一》
C++ 右值引用
block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38
右值的引入
作為在C++11中引入的一個(gè)類型,容易引起誤解的是,右值引用并沒(méi)有說(shuō)明引入是為了什么,是為了解決什么問(wèn)題。
右值引用可以解決以下問(wèn)題
左值和右值來(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)題。
- 上一篇: 数据库系统实训——实验三——子查询与组合
- 下一篇: 万字长文带你一文读完Effective