移动语义(move semantic)和完美转发(perfect forward)
完整原文鏈接:https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/
移動(dòng)語(yǔ)義(move semantic)
通過(guò)移動(dòng)語(yǔ)義,我們可以在沒(méi)有必要的時(shí)候避免復(fù)制。那么在接下來(lái),我們就重點(diǎn)來(lái)談一談移動(dòng)構(gòu)造函數(shù)(move constructor)。相信到這里你已經(jīng)意識(shí)到了,移動(dòng)構(gòu)造函數(shù)的出現(xiàn)就是為了解決復(fù)制構(gòu)造函數(shù)的這個(gè)弊病。所以,其實(shí)移動(dòng)構(gòu)造函數(shù)應(yīng)該和復(fù)制構(gòu)造函數(shù)實(shí)現(xiàn)差不多的功能。那么,它也應(yīng)該是一種構(gòu)造函數(shù)的重載(好廢的廢話(huà)……)。所以,我們可以想象出來(lái),其實(shí)移動(dòng)構(gòu)造函數(shù)大概就會(huì)是這個(gè)樣子:
Test(<KEYWORD> t):arr(t.arr){t.arr = nullptr;}這里解釋一下,通過(guò)移動(dòng)構(gòu)造函數(shù),事實(shí)上我們是做了一個(gè)淺拷貝(shallow copy)。至于要將之前的指針置為空的原因在于,我們的類(lèi)會(huì)在析構(gòu)的時(shí)候delete掉我們的數(shù)組。那么我們淺拷貝出來(lái)的這個(gè)對(duì)象的成員變量(arr指針)就變成了一個(gè)懸掛指針(dangling pointer)。
好了,現(xiàn)在的問(wèn)題變成了,這個(gè)<KEYWORD>究竟是什么?編譯器如何自動(dòng)判斷到底應(yīng)該調(diào)用復(fù)制構(gòu)造函數(shù)(我突然想起來(lái)這個(gè)東西的翻譯貌似應(yīng)該是拷貝構(gòu)造函數(shù),但是既然都已經(jīng)寫(xiě)了這么多了,我就不改了)還是移動(dòng)構(gòu)造函數(shù)呢?
....................
進(jìn)一步探討左值和右值
我們來(lái)考慮下面的情景:
void doWork(TYPE&& param) {// ops and expressions using std::move(param) }這個(gè)代碼是從Scott Meyers的演講當(dāng)中摘取的?,F(xiàn)在的問(wèn)題是:** param是右值嗎? **答案是:不!param是一個(gè)左值。
這里牽扯到一個(gè)概念,即事實(shí)上左值和右值與類(lèi)型是沒(méi)有關(guān)系的,即int既可以是左值,也可以是右值。區(qū)別左值和右值的唯一方法就是其定義,即能否取到地址。在這里,我們明顯可以對(duì)param進(jìn)行取地址操作,所以它是一個(gè)左值。也就是說(shuō),但凡有名字的“右值”,其實(shí)都是左值。這也就是為什么上面的代碼當(dāng)中鼓勵(lì)大家對(duì)所有的變量使用std::move()轉(zhuǎn)成右值的原因。
....................
完美轉(zhuǎn)發(fā)(perfect forward)又是在做什么
我們依然考慮一個(gè)例子:
template <typename T> void func(T t) {cout << "in func" << endl; }template <typename T> void relay(T&& t) {cout << "in relay" << endl;func(t); }int main() {relay(Test()); }在這個(gè)例子當(dāng)中,我們的期待是,我們?cè)趍ain當(dāng)中調(diào)用relay,Test的臨時(shí)對(duì)象作為一個(gè)右值傳入relay,在relay當(dāng)中又被轉(zhuǎn)發(fā)給了func,那這時(shí)候轉(zhuǎn)發(fā)給func的參數(shù)t也應(yīng)當(dāng)是一個(gè)右值。也就是說(shuō),我們希望:當(dāng)relay的參數(shù)是右值的時(shí)候,func的參數(shù)也是右值;當(dāng)relay的參數(shù)是左值的時(shí)候,func的參數(shù)也是左值。
那么現(xiàn)在我們來(lái)運(yùn)行一下這個(gè)程序,我們會(huì)看到,結(jié)果與我們預(yù)想的似乎并不相同:
default constructor in relay copy constructor in func destructor destructor我們看到,在relay當(dāng)中轉(zhuǎn)發(fā)的時(shí)候,調(diào)用了復(fù)制構(gòu)造函數(shù),也就是說(shuō)編譯器認(rèn)為這個(gè)參數(shù)t并不是一個(gè)右值,而是左值。這個(gè)的原因已經(jīng)在上一節(jié)將結(jié)果了,因?yàn)樗幸粋€(gè)名字。那么如果我們想要實(shí)現(xiàn)我們所說(shuō)的,如果傳進(jìn)來(lái)的參數(shù)是一個(gè)左值,則將它作為左值轉(zhuǎn)發(fā)給下一個(gè)函數(shù);如果它是右值,則將其作為右值轉(zhuǎn)發(fā)給下一個(gè)函數(shù),我們應(yīng)該怎么做呢?
這時(shí),我們需要std::forward<T>()。與std::move()相區(qū)別的是,move()會(huì)無(wú)條件的將一個(gè)參數(shù)轉(zhuǎn)換成右值,而forward()則會(huì)保留參數(shù)的左右值類(lèi)型。所以我們的代碼應(yīng)該是這樣:
template <typename T> void func(T t) {cout << "in func " << endl; }template <typename T> void relay(T&& t) {cout << "in relay " << endl;func(std::forward<T>(t)); }現(xiàn)在運(yùn)行的結(jié)果就成為了:
default constructor in relay move constructor in func destructor destructor而如果我們的調(diào)用方法變成:
int main() {Test t;relay(t); }那么輸出就會(huì)變成:
default constructor in relay copy constructor in func destructor destructor完美地實(shí)現(xiàn)了我們所要的轉(zhuǎn)發(fā)效果。
.............
................
后記
C++0x通過(guò)引入許多新的語(yǔ)言特性來(lái)實(shí)現(xiàn)了語(yǔ)言性能的提升,使得本來(lái)就博大精深的一門(mén)語(yǔ)言變得更加的難以學(xué)習(xí)。但是一旦了解,就會(huì)被語(yǔ)言精妙的設(shè)計(jì)所折服。參考資料中給出了更多的關(guān)于左值、右值、左值引用、右值引用、移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā)的例子。我自己實(shí)在是沒(méi)有精力看完所有的這些資料了,各位有興趣的話(huà)可以參閱。
參考資料
?
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的移动语义(move semantic)和完美转发(perfect forward)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: gta5卖车的地方在哪
- 下一篇: 和平精英情侣模式在哪里