C++11新特性——移动语义,右值引用
移動(dòng)語義
有一些類的資源是__不可共享__的,這種類型的對象可以被移動(dòng)但不能被拷貝,如:IO 或 unique_ptr
庫容器、string 和 shared_ptr 支持拷貝和移動(dòng),IO 和 unique_ptr 則只能移動(dòng)不能拷貝。。
右值引用
右值引用是必須綁定到右值的引用,右值引用使用 && 符號,相較于左值引用的& 。右值引用有一個(gè)特性就是其只能綁定到即將銷毀的對象上,因而,可以自由的移動(dòng)右值引用對象中的資源。
左值表示對象的身份,而右值表示對象的值。不能將左值引用(lvalue reference)綁定到需要轉(zhuǎn)型的值、字面量或者返回右值的表達(dá)式上。右值引用則剛好相反:可以將右值引用綁定到以上的值,但不能直接將右值引用綁定到左值。如:
int i = 42; int &r = i; int &&rr = i; //錯(cuò)誤:不能將右值引用綁定到左值上 int &r2 = i * 42; //錯(cuò)誤:不能將左值引用綁定到右值上 const int &r3 = i * 42; //可以將 const 左值引用綁定到任何類型的值上(const/非 const 的左/右值) int &&rr2 = i * 42; //將右值引用綁定到右值上返回左值引用的函數(shù)和賦值、下標(biāo)操作、解引用和前綴自增/自減操作符都是返回左值的表達(dá)式,可將左值引用綁定到這些表達(dá)式的結(jié)果中。
返回非引用類型的函數(shù)與算術(shù)、關(guān)系、位操作和后綴自增/自減的操作符都是返回右值的表達(dá)式,可將右值引用和 const 左值引用綁定到這種表達(dá)式上。
變量是左值
一個(gè)變量就是一個(gè)表達(dá)式,其只有一個(gè)操作數(shù)而沒有操作符。變量表達(dá)式是左值。因而,不能將右值引用綁定到一個(gè)定義為右值引用的變量上。如:
int &&rr1 = 42; int &&rr2 = rr1; //錯(cuò)誤:rr1 是左值,因而不能這樣定義一個(gè)變量就是一個(gè)左值;不能直接將右值引用綁定到一個(gè)變量上,即使這個(gè)變量被定義為右值引用類型也不可以。
但是如果臨時(shí)對象通過一個(gè)接受右值的函數(shù)傳遞給另一個(gè)函數(shù)時(shí),就會(huì)變成左值,因?yàn)檫@個(gè)臨時(shí)對象在傳遞過程中,變成了命名對象。
move庫函數(shù)
template< class T > (C++11 起) typename std::remove_reference<T>::type&& move( T&& t ) noexcept; (C++14 前) template< class T > (C++14 起) constexpr typename std::remove_reference<T>::type&& move( T&& t ) noexcept;可以顯式將左值強(qiáng)轉(zhuǎn)為對應(yīng)的右值引用類型,也可以通過調(diào)用 move 庫函數(shù)來獲取綁定到左值的右值引用,其被定義在 utility 頭文件中。如:
int &&rr3 = std::move(rr1);調(diào)用 move 告知編譯器,以右值方式對象一個(gè)左值。特別需要了解的是調(diào)用 move 將承諾:不會(huì)再次使用 rr1 ,除非是賦值或者析構(gòu)。當(dāng)調(diào)用了 move 之后,不能對這個(gè)對象做任何值上的假設(shè)。可以析構(gòu)或賦值給移動(dòng)后的對象,但在此之前不能使用其值。
使用 move 的代碼應(yīng)該使用 std::move ,而不是 move,這樣做可以避免潛在的名字沖突。
移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值
為了讓我們自己的類可以執(zhí)行移動(dòng)操作,需要定義移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符。這些成員類似于對應(yīng)的拷貝賦值操作,但是他們將從給定對象中偷取資源而不是復(fù)制。
除了移動(dòng)資源,移動(dòng)構(gòu)造函數(shù)需要保證移動(dòng)后的對象的狀態(tài)是析構(gòu)無害的。特別是,一旦資源被移動(dòng)后,原始對象就不再指向移動(dòng)了的資源,這些所有權(quán)被轉(zhuǎn)移給了新創(chuàng)建的對象。如:
StrVec::StrVec(StrVec &&s) noexcept :elements(s.elements), first_free(s.first_free), cap(s.cap) {s.elements = s.first_free = s.cap = nullptr; }與拷貝構(gòu)造函數(shù)不同,移動(dòng)構(gòu)造函數(shù)并不會(huì)分配新資源;其將攫取參數(shù)中的內(nèi)存,在此之后,構(gòu)造函數(shù)體將參數(shù)中的指針都設(shè)置為 nullptr,當(dāng)一個(gè)對象被移動(dòng)后,這個(gè)對象依然存在。最后移動(dòng)后的對象將被析構(gòu),意味著析構(gòu)函數(shù)將在此對象上運(yùn)行。析構(gòu)函數(shù)將釋放其所擁有的資源,如果沒有將指針設(shè)置為 nullptr 的,就會(huì)將移動(dòng)了的資源給釋放掉。
移動(dòng)操作,庫容器和異常
移動(dòng)操作通常不必自己分配資源,所以移動(dòng)操作通常不拋出任何異常。當(dāng)我們寫移動(dòng)操作時(shí),由于其不會(huì)拋出異常,我們應(yīng)當(dāng)告知編譯器這個(gè)事實(shí)。除非編譯器知道這個(gè)事實(shí),它將必須做額外的工作來滿足移動(dòng)構(gòu)造操作將拋出異常。
通過在函數(shù)參數(shù)列表后加上 noexcept ,在構(gòu)造函數(shù)時(shí)則,noexcept 出現(xiàn)在參數(shù)列表后到冒號之間,來告知編譯器一個(gè)函數(shù)不會(huì)拋出異常。如:
class StrVec { public:StrVec(StrVec &&) noexcept; }; StrVec::StrVec(StrVec &&s) noexcept : { ... }必須同時(shí)在類體內(nèi)的聲明處和定義處同時(shí)指定 noexcept。
移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符,如果都不允許拋出異常,那么就應(yīng)該被指定為 noexcept。
告知移動(dòng)操作不拋出異常是由于兩個(gè)不相關(guān)的事實(shí):第一,盡管移動(dòng)操作通常不拋出異常,它們可以這樣做。第二,有些庫容器在元素是否會(huì)在構(gòu)建時(shí)拋出異常有不同的表現(xiàn),如:vector 只有在知道元素類型的移動(dòng)構(gòu)造函數(shù)不會(huì)拋出異常才使用移動(dòng)構(gòu)造函數(shù),否則將必須使用拷貝構(gòu)造函數(shù);
移動(dòng)賦值操作符
StrVec& StrVec::operator=(StrVec &&rhs) noexcept {if (this == &rhs)return *this;free();elements = rhs.elements;first_free = rhs.first_free;cap = rhs.cap;rhs.elements = rhs.first_free = rhs.cap = nullptr;return *this; }移動(dòng)賦值操作符不拋出異常應(yīng)當(dāng)用 noexcept 修飾,與拷貝賦值操作符一樣需要警惕自賦值的可能性。移動(dòng)賦值操作符同時(shí)聚合了析構(gòu)函數(shù)和移動(dòng)構(gòu)造函數(shù)的工作:其將釋放左操作數(shù)的內(nèi)存,并且占有右操作數(shù)的內(nèi)存,并將右操作數(shù)的指針設(shè)為 nullptr。
移動(dòng)后的對象必須是可以析構(gòu)的
移動(dòng)對象并不會(huì)析構(gòu)那個(gè)對象,有時(shí)在移動(dòng)操作完成后,被移動(dòng)的對象將被銷毀。因而,當(dāng)我們寫移動(dòng)操作時(shí),必須保證移動(dòng)后的對象的狀態(tài)是可以析構(gòu)的。StrVec 通過將其指針設(shè)置為 nullptr 來滿足此要求。
除了讓對象處于可析構(gòu)狀態(tài),移動(dòng)操作必須保證對象處于有效狀態(tài)。通常來說,有效狀態(tài)就是可以安全的賦予新值或者使用在不依賴當(dāng)前值的方式下。另一方面,移動(dòng)操作對于遺留在移動(dòng)后的對象中的值沒有什么特別要求,所以,程序不應(yīng)該依賴于移動(dòng)后對象的值。
例如,從庫 string 和容器對象中移動(dòng)資源后,移動(dòng)后對象的狀態(tài)將保持有效。可以在移動(dòng)后對象上調(diào)用 empty 或 size 函數(shù),然而,并不保證得到的結(jié)果是空的。可以期望一個(gè)移動(dòng)后對象是空的,但是這并不保證。
以上 StrVec 的移動(dòng)操作將移動(dòng)后對象留在一個(gè)與默認(rèn)初始化一樣的狀態(tài)。因而,這個(gè) StrVec 的所有操作將與默認(rèn)初始化的 StrVec 的操作完全一樣。其它類,有著更加復(fù)雜的內(nèi)部結(jié)構(gòu),也許會(huì)表現(xiàn)的不一致。
在移動(dòng)后操作,移動(dòng)后對象必須保證在一個(gè)有效狀態(tài),并且可以析構(gòu),但是用戶不能對其值做任何假設(shè)。
*合成移動(dòng)操作
編譯器會(huì)為對象合成移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符。然而,在什么情況下合成移動(dòng)操作與合成拷貝操作是十分不同的。
與拷貝操作不同的,對于某些類來說,編譯器根本不合成任何移動(dòng)操作。特別是,如果一個(gè)類定義自己的拷貝構(gòu)造函數(shù)、拷貝賦值操作符或析構(gòu)函數(shù),移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符是不會(huì)合成的。作為結(jié)果,有些類是沒有移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值操作符。同樣,當(dāng)一個(gè)類沒有移動(dòng)操作時(shí),對應(yīng)的拷貝操作將通過函數(shù)匹配被用于替代移動(dòng)操作。
編譯器只會(huì)在類沒有定義任何拷貝控制成員并且所有的非 static 數(shù)據(jù)成員都是可移動(dòng)的情況下才會(huì)合成移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符。編譯器可以移動(dòng)內(nèi)置類型的成員,亦可以移動(dòng)具有對應(yīng)移動(dòng)操作的類類型成員。
移動(dòng)操作不會(huì)隱式被定義為刪除的,而是根本不定義,當(dāng)沒有移動(dòng)構(gòu)造函數(shù)時(shí),重載將選擇拷貝構(gòu)造函數(shù)。當(dāng)用 =default 要求編譯器生成時(shí),如果編譯器無法移動(dòng)所有成員,將會(huì)生成一個(gè)刪除的移動(dòng)操作。被刪除的函數(shù)不是說不能被用于函數(shù)重載,而是說當(dāng)其是重載解析時(shí)最合適的候選函數(shù)時(shí),將是編譯錯(cuò)誤。
- 與拷貝構(gòu)造函數(shù)不同,當(dāng)類有一個(gè)成員定義了自己的拷貝構(gòu)造函數(shù),但是沒有定義移動(dòng)構(gòu)造函數(shù)時(shí)使用拷貝構(gòu)造函數(shù)。當(dāng)成員沒有定義自己的拷貝操作但是編譯器無法為其合成移動(dòng)構(gòu)造函數(shù)時(shí),其移動(dòng)構(gòu)造函數(shù)被定義為被刪除的。對于移動(dòng)賦值操作符是一樣的;
- 如果類有一個(gè)成員其移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值操作符是被刪除的或不可訪問的,其移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值操作符被定義為被刪除的;
- 與拷貝構(gòu)造函數(shù)一樣,如果其析構(gòu)函數(shù)是被刪除的或不可訪問的,移動(dòng)構(gòu)造函數(shù)被定義為被刪除的;
- 與拷貝賦值操作符一樣,如果其有一個(gè) const 或引用成員,移動(dòng)賦值操作被定義為刪除的;
如果一個(gè)類定義自己的移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值操作符,那么合成的拷貝構(gòu)造函數(shù)和拷貝賦值操作符都將被定義為被刪除的。
右值移動(dòng),左值拷貝
當(dāng)一個(gè)類既有移動(dòng)構(gòu)造函數(shù)又有拷貝構(gòu)造函數(shù),編譯器使用常規(guī)的函數(shù)匹配來決定使用哪個(gè)構(gòu)造函數(shù)。拷貝構(gòu)造函數(shù)通常使用 const StrVec 引用類型作為參數(shù),因而,可以匹配可以轉(zhuǎn)為 StrVec 類型的對象參數(shù)。而移動(dòng)構(gòu)造函數(shù)則使用 StrVec && 作為參數(shù),因而,只能使用非 const 的右值。如果調(diào)用拷貝形式的,需要將參數(shù)轉(zhuǎn)為 const 的,而移動(dòng)形式的卻是精確匹配,因而,右值將調(diào)用移動(dòng)形式的。
右值在無法被移動(dòng)時(shí)進(jìn)行拷貝
如果一個(gè)類有拷貝構(gòu)造函數(shù),但是沒有定義移動(dòng)構(gòu)造函數(shù),在這種情況下編譯不會(huì)合成移動(dòng)構(gòu)造函數(shù),意味著類只有拷貝構(gòu)造函數(shù)而沒有移動(dòng)構(gòu)造函數(shù)。如果一個(gè)類沒有移動(dòng)構(gòu)造函數(shù),函數(shù)匹配保證即便是嘗試使用 move 來移動(dòng)對象時(shí),它們依然會(huì)被拷貝。
class Foo { public:Foo() = default;Foo(const Foo&); //拷貝構(gòu)造函數(shù) }; Foo x; Foo y(x); //拷貝構(gòu)造函數(shù);x 是左值 Foo z(std::move(x)); //拷貝構(gòu)造函數(shù);因?yàn)闆]有移動(dòng)構(gòu)造函數(shù)調(diào)用 move(x) 時(shí)返回 Foo&& ,Foo 的拷貝構(gòu)造函數(shù)是可行的,因?yàn)榭梢詫?Foo&& 轉(zhuǎn)為 const Foo& ,因而,使用拷貝構(gòu)造函數(shù)來初始化 z 。
使用拷貝構(gòu)造函數(shù)來替換移動(dòng)構(gòu)造函數(shù)通常是安全的,對于賦值操作符來說是一樣的。拷貝構(gòu)造符合移動(dòng)構(gòu)造函數(shù)的先決條件:它將拷貝給定的對象,并且不會(huì)改變其狀態(tài),這樣原始對象將保持在有效狀態(tài)內(nèi)。
拷貝和交換賦值操作與移動(dòng)
class HasPtr { public:HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}HasPtr& operator=(HasPtr rhs){swap(*this, rhs);return *this;} };賦值操作符的參數(shù)是非引用類型的,所以參數(shù)是拷貝初始化的。根據(jù)參數(shù)的類型,拷貝初始化可能使用拷貝構(gòu)造函數(shù)也可能使用移動(dòng)構(gòu)造函數(shù)。左值將被拷貝,右值將被移動(dòng)。因而,這個(gè)移動(dòng)操作符既是拷貝賦值操作符又是移動(dòng)賦值操作符。如:
hp = hp2; hp = std::move(hp2);所有五個(gè)拷貝控制成員應(yīng)該被當(dāng)做一個(gè)整體:通常,如果一個(gè)類定義了其中任何一個(gè)操作,它通常需要定義所有成員。有些類必須定義拷貝構(gòu)造函數(shù),拷貝賦值操作符和析構(gòu)函數(shù)才能正確工作。這種類通常有一個(gè)資源是拷貝成員必須拷貝的,通常拷貝資源需要做很多額外的工作,定義移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值操作符可以避免在不需要拷貝的情況的額外工作。
移動(dòng)迭代器
在新標(biāo)準(zhǔn)中,定義了移動(dòng)迭代器(move iterator)適配器。移動(dòng)迭代器通過改變迭代器的解引用操作來適配給定的迭代器。通常,迭代器解引用返回元素的左值引用,與其它迭代器不同,解引用移動(dòng)迭代器返回右值引用。調(diào)用函數(shù) make_move_iterator 將常規(guī)迭代器變成移動(dòng)迭代器,移動(dòng)迭代器的操作與原始迭代器操作基本一樣,因而可以將移動(dòng)迭代器傳給 uninitialized_copy 函數(shù)。如:
uninitialized_copy(make_move_iterator(begin()), make_move_iterator(end()));值得一提的是標(biāo)準(zhǔn)庫沒有說哪些算法可以使用移動(dòng)迭代器,哪些不可以。因?yàn)橐苿?dòng)對象會(huì)破壞原始對象,所以將移動(dòng)迭代器傳給那些不會(huì)在移動(dòng)后訪問其值的算法才合適。
慎用移動(dòng)操作:由于移動(dòng)后的對象處于中間狀態(tài),在對象上調(diào)用 std::move 是很危險(xiǎn)的。當(dāng)調(diào)用 move 后,必須保證沒有別的用戶使用移動(dòng)后對象。
謹(jǐn)慎克制的在類內(nèi)使用 move 可以提供重大的性能提升,在用戶代碼中使用 move 則更可能導(dǎo)致難以定位的 bug,相比較得到的性能提升是不值得的。
在類實(shí)現(xiàn)代碼外使用 std::move,必須是在確實(shí)需要移動(dòng)操作,并且保證移動(dòng)是安全的。
右值引用和成員函數(shù)
除了構(gòu)造函數(shù)和賦值操作符外提供拷貝和移動(dòng)版本亦會(huì)受益。這種可以移動(dòng)的成員函數(shù)中一個(gè)使用 const 左值引用,另一個(gè)使用非 const 右值引用。如:
void push_back(const X&); //拷貝:綁定到任何類型的 X void push_back(X&&); //移動(dòng):綁定到可修改的右值 X可以傳遞任何可以轉(zhuǎn)換為類型 X 的對象給拷貝版本,這個(gè)版本從參數(shù)中拷貝數(shù)據(jù)。只能將非 const 右值傳遞給移動(dòng)版本。此版本比拷貝版本更好的匹配非 const 右值(精確匹配),因而,在函數(shù)匹配中將是更優(yōu)的,并且可以自由的從參數(shù)中移動(dòng)資源。
通常上面這種重載方式不會(huì)使用 const X&& 和 X& 類型的參數(shù),原因在于移動(dòng)數(shù)據(jù)要求對象是非 const 的,而拷貝數(shù)據(jù)則應(yīng)該是 const 的。
以拷貝或移動(dòng)的方式對函數(shù)進(jìn)行重載,常用的做法是一個(gè)版本使用 const T& 為參數(shù),另外一個(gè)版本使用 T&& 為參數(shù)。
右值與左值引用的成員函數(shù)
有些成員函數(shù)是只允許左值調(diào)用的,右值是不能調(diào)用的,如:在新標(biāo)準(zhǔn)前可以給兩個(gè)字符串拼接的結(jié)果賦值:s1 + s2 = "wow!"; ,在新標(biāo)準(zhǔn)中可以強(qiáng)制要求賦值操作符的左操作數(shù)是左值,通過在參數(shù)列表后放置引用修飾符(reference qualifier)可以指示 this 的左值/右值特性。如:
class Foo { public:Foo& operator=(const Foo&) &; }; Foo& Foo::operator=(const Foo& rhs) & { return *this; }引用修飾符可以是 & 或者 && 用于表示 this 指向左值或右值。與 const 修飾符一樣,引用修飾符必須出現(xiàn)在非 static 成員函數(shù)的聲明和定義處。被 & 修飾的函數(shù)只能被左值調(diào)用,被 && 修飾的函數(shù)只能被右值調(diào)用。
一個(gè)函數(shù)既可以有 const 也可以有引用修飾符,在這種情況下,引用修飾符在 const 修復(fù)符的后面。如:
class Foo { public:Foo someMem() const &; };重載帶引用修飾符的成員函數(shù)
可以通過函數(shù)的引用修飾符進(jìn)行重載,這與常規(guī)的函數(shù)重載是一樣的,&& 可以在可修改的右值上調(diào)用,const & 可以在任何類型的對象上調(diào)用。如:
class Foo { public:Foo sorted() &&; //可以在可修改的右值上調(diào)用Foo sorted() const &; //可以在任何類型的 Foo 上調(diào)用 };當(dāng)定義具有相同名字和相同參數(shù)列表的成員函數(shù)時(shí),必須同時(shí)提供引用修飾符或者都不提供引用修飾符,如果只在其中一些提供,而另外一些不提供就是編譯錯(cuò)誤。如:
class Foo { public:Foo sorted() &&;Foo sorted() const; //錯(cuò)誤:必須提供引用修飾符//全不提供引用修飾符是合法的using Comp = bool(const int&, const int&);Foo sorted(Comp*);Foo sorted(Comp*) const; };精確傳遞 (Perfect Forwarding)
本文采用精確傳遞表達(dá)這個(gè)意思。Perfect Forwarding也被翻譯成完美轉(zhuǎn)發(fā),精準(zhǔn)轉(zhuǎn)發(fā)等,說的都是一個(gè)意思。
精確傳遞適用于這樣的場景:需要將一組參數(shù)原封不動(dòng)的傳遞給另一個(gè)函數(shù)。
“原封不動(dòng)”不僅僅是參數(shù)的值不變,在 C++ 中,除了參數(shù)值之外,還有一下兩組屬性:
左值/右值和 const/non-const。 精確傳遞就是在參數(shù)傳遞過程中,所有這些屬性和參數(shù)值都不能改變。在泛型函數(shù)中,這樣的需求非常普遍。
下面舉例說明。函數(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&對于一個(gè)參數(shù)就要重載兩次,也就是函數(shù)重載的次數(shù)和參數(shù)的個(gè)數(shù)是一個(gè)正比的關(guān)系。這個(gè)函數(shù)的定義次數(shù)對于程序員來說,是非常低效的。我們看看右值引用如何幫助我們解決這個(gè)問題 :
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è)解決方案不是簡潔優(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è)引用符號,但它對 C++ 軟件設(shè)計(jì)和類庫的設(shè)計(jì)有非常大的影響。它既能簡化代碼,又能提高程序運(yùn)行效率。每一個(gè) C++ 軟件設(shè)計(jì)師和程序員都應(yīng)該理解并能夠應(yīng)用它。我們在設(shè)計(jì)類的時(shí)候如果有動(dòng)態(tài)申請的資源,也應(yīng)該設(shè)計(jì)轉(zhuǎn)移構(gòu)造函數(shù)和轉(zhuǎn)移拷貝函數(shù)。在設(shè)計(jì)類庫時(shí),還應(yīng)該考慮 std::move 的使用場景并積極使用它。
關(guān)鍵術(shù)語
-
拷貝-交換(copy and swap):一種書寫賦值操作符的技術(shù),先將右操作數(shù)拷貝到參數(shù)中,然后調(diào)用 swap 將其與左操作數(shù)進(jìn)行交換;
-
拷貝賦值操作符(copy-assignment operator):拷貝賦值操作符與本類的 const 引用對象作為參數(shù),返回對象的引用。如果類不定義拷貝賦值操作符,編譯器將合成一個(gè);
-
拷貝構(gòu)造函數(shù)(copy constructor):將新對象初始化為本類的另一個(gè)對象的副本的構(gòu)造函數(shù)。拷貝構(gòu)造函數(shù)將在以非引用方式傳遞參數(shù)或從函數(shù)中返回時(shí)默認(rèn)調(diào)用。如果類不定義的話,編譯器將合成一個(gè);
-
拷貝控制(copy control):用于控制對象被拷貝、移動(dòng)、賦值和銷毀時(shí)應(yīng)當(dāng)做什么的成員函數(shù)。如果類不定這些函數(shù),編譯器將在合適的時(shí)候合成它們;
-
拷貝初始化(copy initialization):使用 = 形式的初始化,或者當(dāng)傳遞參數(shù)、按值形式返回值,或者初始化數(shù)組或聚合類時(shí),將進(jìn)行拷貝初始化。拷貝初始化將根據(jù)初始值是左值還是右值,使用拷貝構(gòu)造函數(shù)或者移動(dòng)構(gòu)造函數(shù);
-
被刪除的函數(shù)(deleted function):不被使用的函數(shù),通過 =delete 來刪除函數(shù)。使用被刪除的函數(shù)是告知編譯器在進(jìn)行函數(shù)匹配時(shí),如果匹配到被刪除的函數(shù)就報(bào)編譯器錯(cuò)誤;
-
析構(gòu)函數(shù)(destructor):當(dāng)對象離開作用域時(shí)調(diào)用的特殊成員函數(shù)來清理對象。編譯器自動(dòng)銷毀每個(gè)數(shù)據(jù)成員,類成員通過調(diào)用其析構(gòu)函數(shù)進(jìn)行銷毀,內(nèi)置類型或符合類型將不做任何析構(gòu)操作,特別是指向動(dòng)態(tài)對象的指針不會(huì)被自動(dòng) delete;
-
逐個(gè)成員拷貝/賦值(memberwise copy/assign):合成的拷貝/移動(dòng)構(gòu)造函數(shù)和拷貝/移動(dòng)賦值操作符的運(yùn)作方式。依次對所有的數(shù)據(jù)成員,拷貝/移動(dòng)構(gòu)造函數(shù)通過從參數(shù)中拷貝/移動(dòng)對應(yīng)的成員進(jìn)行初始化;拷貝/移動(dòng)賦值操作符則依次對右操作數(shù)的各個(gè)成員進(jìn)行拷貝/移動(dòng)賦值;內(nèi)置類型的成員是直接進(jìn)行初始化或賦值的。類類型成員則調(diào)用對應(yīng)的拷貝/移動(dòng)構(gòu)造函數(shù)或拷貝/移動(dòng)賦值操作符;
-
move 函數(shù)(move function):用于將左值綁定到右值引用的庫函數(shù)。調(diào)用 move 將隱式保證不會(huì)使用移動(dòng)后的對象值,唯一的操作是析構(gòu)或者賦予新值;
-
移動(dòng)賦值操作符(move-assignment operator):參數(shù)是右值引用的賦值操作符版本。通常移動(dòng)賦值操作符將其右操作數(shù)的數(shù)據(jù)移動(dòng)到左操作數(shù)。在賦值后,必須保證可以安全的析構(gòu)掉右操作數(shù);
-
移動(dòng)構(gòu)造函數(shù)(move constructor):以右值引用為參數(shù)的構(gòu)造函數(shù)。移動(dòng)構(gòu)造函數(shù)將參數(shù)中的數(shù)據(jù)移動(dòng)到新創(chuàng)建的對象中。在移動(dòng)后,必須保證可以安全地析構(gòu)掉右操作數(shù);
-
移動(dòng)迭代器(move iterator):迭代器適配器,包裝一個(gè)迭代器,當(dāng)其解引用時(shí)返回右值引用;
-
右值引用(rvalue reference):對即將被銷毀的對象的引用;
總結(jié)
以上是生活随笔為你收集整理的C++11新特性——移动语义,右值引用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTML5如何添加图片遮罩,带有HTML
- 下一篇: C++11多线程----线程管理