C++11中移動(dòng)構(gòu)造函數(shù)案例代碼
1. 拷貝構(gòu)造函數(shù)中的深拷貝問(wèn)題
在 C++ 98/03 標(biāo)準(zhǔn)中,如果想用其它對(duì)象初始化一個(gè)同類(lèi)的新對(duì)象,只能借助類(lèi)中的拷貝構(gòu)造函數(shù)??截悩?gòu)造函數(shù)的實(shí)現(xiàn)原理很簡(jiǎn)單,就是為新對(duì)象復(fù)制一份和其它對(duì)象一模一樣的數(shù)據(jù)。需要注意的是,當(dāng)類(lèi)中擁有指針類(lèi)型的成員變量時(shí),拷貝構(gòu)造函數(shù)中需要以深拷貝(而非淺拷貝)的方式復(fù)制該指針成員。
舉個(gè)例子:
#include <iostream> using namespace std; class demo{ public: demo():num(new int(0)){ cout<<"construct!"<<endl; } //拷貝構(gòu)造函數(shù) demo(const demo &d):num(new int(*d.num)){ cout<<"copy construct!"<<endl; } ~demo(){ cout<<"class destruct!"<<endl; } private: int *num; }; demo get_demo(){ return demo(); } int main(){ demo a = get_demo(); return 0; }
如上所示,我們?yōu)?demo 類(lèi)自定義了一個(gè)拷貝構(gòu)造函數(shù)。該函數(shù)在拷貝 d.num 指針成員時(shí),必須采用深拷貝的方式,即拷貝該指針成員本身的同時(shí),還要拷貝指針指向的內(nèi)存資源。否則一旦多個(gè)對(duì)象中的指針成員指向同一塊堆空間,這些對(duì)象析構(gòu)時(shí)就會(huì)對(duì)該空間釋放多次,這是不允許的。
可以看到,程序中定義了一個(gè)可返回 demo 對(duì)象的 get_demo() 函數(shù),用于在 main() 主函數(shù)中初始化 a 對(duì)象,其整個(gè)初始化的流程包含以下幾個(gè)階段:
- 執(zhí)行 get_demo() 函數(shù)內(nèi)部的 demo() 語(yǔ)句,即調(diào)用 demo 類(lèi)的默認(rèn)構(gòu)造函數(shù)生成一個(gè)匿名對(duì)象;
- 執(zhí)行 return demo() 語(yǔ)句,會(huì)調(diào)用拷貝構(gòu)造函數(shù)復(fù)制一份之前生成的匿名對(duì)象,并將其作為 get_demo() 函數(shù)的返回值(函數(shù)體執(zhí)行完畢之前,匿名對(duì)象會(huì)被析構(gòu)銷(xiāo)毀);
- 執(zhí)行 a = get_demo() 語(yǔ)句,再調(diào)用一次拷貝構(gòu)造函數(shù),將之前拷貝得到的臨時(shí)對(duì)象復(fù)制給 a(此行代碼執(zhí)行完畢,get_demo() 函數(shù)返回的對(duì)象會(huì)被析構(gòu));
- 程序執(zhí)行結(jié)束前,會(huì)自行調(diào)用 demo 類(lèi)的析構(gòu)函數(shù)銷(xiāo)毀 a。
注意,目前多數(shù)編譯器都會(huì)對(duì)程序中發(fā)生的拷貝操作進(jìn)行優(yōu)化,因此如果我們使用 VS 2017、codeblocks 等這些編譯器運(yùn)行此程序時(shí),看到的往往是優(yōu)化后的輸出結(jié)果:
construct!
class destruct!
而同樣的程序,如果在 Linux 上使用g++ demo.cpp -fno-elide-constructors
命令運(yùn)行(其中 demo.cpp 是程序文件的名稱(chēng)),就可以看到完整的輸出結(jié)果:
construct! <-- 執(zhí)行 demo()
copy construct! <-- 執(zhí)行 return demo()
class destruct! <-- 銷(xiāo)毀 demo() 產(chǎn)生的匿名對(duì)象
copy construct! <-- 執(zhí)行 a = get_demo()
class destruct! <-- 銷(xiāo)毀 get_demo() 返回的臨時(shí)對(duì)象
class destruct! <-- 銷(xiāo)毀 a
如上所示,利用拷貝構(gòu)造函數(shù)實(shí)現(xiàn)對(duì) a 對(duì)象的初始化,底層實(shí)際上進(jìn)行了 2 次拷貝(而且是深拷貝)操作。當(dāng)然,對(duì)于僅申請(qǐng)少量堆空間的臨時(shí)對(duì)象來(lái)說(shuō),深拷貝的執(zhí)行效率依舊可以接受,但如果臨時(shí)對(duì)象中的指針成員申請(qǐng)了大量的堆空間,那么 2 次深拷貝操作勢(shì)必會(huì)影響 a 對(duì)象初始化的執(zhí)行效率。
事實(shí)上,此問(wèn)題一直存留在以 C++ 98/03 標(biāo)準(zhǔn)編寫(xiě)的 C++ 程序中。由于臨時(shí)變量的產(chǎn)生、銷(xiāo)毀以及發(fā)生的拷貝操作本身就是很隱晦的(編譯器對(duì)這些過(guò)程做了專(zhuān)門(mén)的優(yōu)化),且并不會(huì)影響程序的正確性,因此很少進(jìn)入程序員的視野。
那么當(dāng)類(lèi)中包含指針類(lèi)型的成員變量,使用其它對(duì)象來(lái)初始化同類(lèi)對(duì)象時(shí),怎樣才能避免深拷貝導(dǎo)致的效率問(wèn)題呢?C++11 標(biāo)準(zhǔn)引入了解決方案,該標(biāo)準(zhǔn)中引入了右值引用的語(yǔ)法,借助它可以實(shí)現(xiàn)移動(dòng)語(yǔ)義。
2. C++移動(dòng)構(gòu)造函數(shù)(移動(dòng)語(yǔ)義的具體實(shí)現(xiàn))
所謂移動(dòng)語(yǔ)義,指的就是以移動(dòng)而非深拷貝的方式初始化含有指針成員的類(lèi)對(duì)象。簡(jiǎn)單的理解,移動(dòng)語(yǔ)義指的就是將其他對(duì)象(通常是臨時(shí)對(duì)象)擁有的內(nèi)存資源“移為已用”。
以前面程序中的 demo 類(lèi)為例,該類(lèi)的成員都包含一個(gè)整形的指針成員,其默認(rèn)指向的是容納一個(gè)整形變量的堆空間。當(dāng)使用 get_demo() 函數(shù)返回的臨時(shí)對(duì)象初始化 a 時(shí),我們只需要將臨時(shí)對(duì)象的 num 指針直接淺拷貝給 a.num,然后修改該臨時(shí)對(duì)象中 num 指針的指向(通常另其指向 NULL),這樣就完成了 a.num 的初始化。
事實(shí)上,對(duì)于程序執(zhí)行過(guò)程中產(chǎn)生的臨時(shí)對(duì)象,往往只用于傳遞數(shù)據(jù)(沒(méi)有其它的用處),并且會(huì)很快會(huì)被銷(xiāo)毀。因此在使用臨時(shí)對(duì)象初始化新對(duì)象時(shí),我們可以將其包含的指針成員指向的內(nèi)存資源直接移給新對(duì)象所有,無(wú)需再新拷貝一份,這大大提高了初始化的執(zhí)行效率。
例如,下面程序?qū)?demo 類(lèi)進(jìn)行了修改:
#include <iostream> using namespace std; class demo{ public: demo():num(new int(0)){ cout<<"construct!"<<endl; } demo(const demo &d):num(new int(*d.num)){ cout<<"copy construct!"<<endl; } //添加移動(dòng)構(gòu)造函數(shù) demo(demo &&d):num(d.num){ d.num = NULL; cout<<"move construct!"<<endl; } ~demo(){ cout<<"class destruct!"<<endl; } private: int *num; }; demo get_demo(){ return demo(); } int main(){ demo a = get_demo(); return 0; }
可以看到,在之前 demo 類(lèi)的基礎(chǔ)上,我們又手動(dòng)為其添加了一個(gè)構(gòu)造函數(shù)。和其它構(gòu)造函數(shù)不同,此構(gòu)造函數(shù)使用右值引用形式的參數(shù),又稱(chēng)為移動(dòng)構(gòu)造函數(shù)。并且在此構(gòu)造函數(shù)中,num 指針變量采用的是淺拷貝的復(fù)制方式,同時(shí)在函數(shù)內(nèi)部重置了 d.num,有效避免了“同一塊對(duì)空間被釋放多次”情況的發(fā)生。
在 Linux 系統(tǒng)中使用g++ demo.cpp -o demo.exe -std=c++0x -fno-elide-constructors
命令執(zhí)行此程序,輸出結(jié)果為:
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!
通過(guò)執(zhí)行結(jié)果我們不難得知,當(dāng)為 demo 類(lèi)添加移動(dòng)構(gòu)造函數(shù)之后,使用臨時(shí)對(duì)象初始化 a 對(duì)象過(guò)程中產(chǎn)生的 2 次拷貝操作,都轉(zhuǎn)由移動(dòng)構(gòu)造函數(shù)完成。
我們知道,非 const 右值引用只能操作右值,程序執(zhí)行結(jié)果中產(chǎn)生的臨時(shí)對(duì)象(例如函數(shù)返回值、lambda 表達(dá)式等)既無(wú)名稱(chēng)也無(wú)法獲取其存儲(chǔ)地址,所以屬于右值。當(dāng)類(lèi)中同時(shí)包含拷貝構(gòu)造函數(shù)和移動(dòng)構(gòu)造函數(shù)時(shí),如果使用臨時(shí)對(duì)象初始化當(dāng)前類(lèi)的對(duì)象,編譯器會(huì)優(yōu)先調(diào)用移動(dòng)構(gòu)造函數(shù)來(lái)完成此操作。只有當(dāng)類(lèi)中沒(méi)有合適的移動(dòng)構(gòu)造函數(shù)時(shí),編譯器才會(huì)退而求其次,調(diào)用拷貝構(gòu)造函數(shù)。
在實(shí)際開(kāi)發(fā)中,通常在類(lèi)中自定義移動(dòng)構(gòu)造函數(shù)的同時(shí),會(huì)再為其自定義一個(gè)適當(dāng)?shù)目截悩?gòu)造函數(shù),由此當(dāng)用戶利用右值初始化類(lèi)對(duì)象時(shí),會(huì)調(diào)用移動(dòng)構(gòu)造函數(shù);使用左值(非右值)初始化類(lèi)對(duì)象時(shí),會(huì)調(diào)用拷貝構(gòu)造函數(shù)。
那么,如果使用左值初始化同類(lèi)對(duì)象,但也想調(diào)用移動(dòng)構(gòu)造函數(shù)完成,有沒(méi)有辦法可以實(shí)現(xiàn)呢?
默認(rèn)情況下,左值初始化同類(lèi)對(duì)象只能通過(guò)拷貝構(gòu)造函數(shù)完成,如果想調(diào)用移動(dòng)構(gòu)造函數(shù),則必須使用右值進(jìn)行初始化。C++11 標(biāo)準(zhǔn)中為了滿足用戶使用左值初始化同類(lèi)對(duì)象時(shí)也通過(guò)移動(dòng)構(gòu)造函數(shù)完成的需求,新引入了 std::move() 函數(shù),它可以將左值強(qiáng)制轉(zhuǎn)換成對(duì)應(yīng)的右值,由此便可以使用移動(dòng)構(gòu)造函數(shù)。
到此這篇關(guān)于C++11中移動(dòng)構(gòu)造函數(shù)的文章就介紹到這了,更多相關(guān)C++11移動(dòng)構(gòu)造函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++編程中break語(yǔ)句和continue語(yǔ)句的學(xué)習(xí)教程
這篇文章主要介紹了C++編程中break語(yǔ)句和continue語(yǔ)句的學(xué)習(xí)教程,break和continue是C++循環(huán)控制中的基礎(chǔ)語(yǔ)句,需要的朋友可以參考下2016-01-01visual studio code 配置C++開(kāi)發(fā)環(huán)境的教程詳解 (windows 開(kāi)發(fā)環(huán)境)
這篇文章主要介紹了 windows 開(kāi)發(fā)環(huán)境下visual studio code 配置C++開(kāi)發(fā)環(huán)境的圖文教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03C語(yǔ)言結(jié)構(gòu)體(struct)的詳細(xì)講解
C語(yǔ)言中,結(jié)構(gòu)體類(lèi)型屬于一種構(gòu)造類(lèi)型(其他的構(gòu)造類(lèi)型還有:數(shù)組類(lèi)型,聯(lián)合類(lèi)型),下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言結(jié)構(gòu)體(struct)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03C++?LeetCode0547題解省份數(shù)量圖的連通分量
這篇文章主要為大家介紹了C++?LeetCode0547題解省份數(shù)量圖的連通分量示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12C++歸并法+快速排序?qū)崿F(xiàn)鏈表排序的方法
這篇文章主要介紹了C++歸并法+快速排序?qū)崿F(xiàn)鏈表排序的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04