談?wù)凜++中的單例
寫C++的時(shí)候用到單例,于是很自然的寫出如下的代碼:
namespace tlanyan { class Foo { private: static Foo* _instance; Foo() {} // other members public: static Foo* getInstance() { if (_instance == NULL) { _instance = new Foo(); } return _instance; } ~Foo() { // clean codes } // other members and codes }; Foo* Foo::_instance = NULL; }
代碼的本意:靜態(tài)成員函數(shù)getInstance獲取單例指針,并且在析構(gòu)函數(shù)中做一些收尾工作。
運(yùn)行代碼后發(fā)現(xiàn)析構(gòu)函數(shù)死活不執(zhí)行,難道一個(gè)單例模式都能寫錯(cuò)?反復(fù)確認(rèn),沒(méi)發(fā)現(xiàn)問(wèn)題所在,于是上萬(wàn)能的StackOverflow上找原因。正好有伙計(jì)有同樣的疑惑,有哥們給出了一個(gè)可行的方案。根據(jù)其答案修改代碼如下:
namespace tlanyan { class Foo { private: Foo() {} // other members public: static Foo& getInstance() { static Foo _instance; return _instance; } ~Foo() { // clean codes } // other members and codes }; }
對(duì)比前一段代碼,主要改動(dòng)是移除了靜態(tài)指針成員,改用函數(shù)內(nèi)的靜態(tài)成員。由于_instance是函數(shù)內(nèi)的靜態(tài)成員,在首次調(diào)用時(shí)被初始化(感謝無(wú)參構(gòu)造函數(shù)),之后調(diào)用將略過(guò)初始化而執(zhí)行后續(xù)代碼;函數(shù)返回實(shí)例的引用,故而每次調(diào)用得到的是同一個(gè)對(duì)象,達(dá)到了單例的目的;程序執(zhí)行結(jié)束后,實(shí)例的析構(gòu)函數(shù)被自動(dòng)調(diào)用,析構(gòu)函數(shù)中的代碼正確執(zhí)行。
問(wèn)題解決了,但什么原因造成第一段單例代碼的析構(gòu)函數(shù)不執(zhí)行呢?
這是由于C++持有對(duì)象的方式造成的(或者說(shuō)C++允許程序員手動(dòng)控制內(nèi)存引起)。Java/PHP等帶有回收機(jī)制的語(yǔ)言,持有對(duì)象的方式是通過(guò)指針,程序員申請(qǐng)對(duì)象后會(huì)自動(dòng)分配內(nèi)存,系統(tǒng)負(fù)責(zé)跟蹤和回收無(wú)用的對(duì)象和存在。C++允許開(kāi)發(fā)人員以變量的方式持有對(duì)象,例如:Foo foo [= Foo(args)]
。變量初始化后獲得對(duì)象的引用,離開(kāi)作用域后,系統(tǒng)銷毀執(zhí)行棧,對(duì)象自動(dòng)被析構(gòu)。C++也可以以指針的形式獲得對(duì)象的引用: Foo* foo = new Foo(args)
。這種方式分配的對(duì)象,需要開(kāi)發(fā)人員手動(dòng)管理。如果不執(zhí)行delete,對(duì)象和分配的內(nèi)存將一直存在,直到程序退出后,才由操作系統(tǒng)回收。如下代碼可說(shuō)明這點(diǎn):
namespace tlanyan { void foo() { Foo foo; // 聲明變量,編譯器會(huì)自動(dòng)初始化變量 Foo* ptr = new Foo(); // 聲明對(duì)象指針,同時(shí)為對(duì)象開(kāi)辟內(nèi)存 // awesome codes return; // 離開(kāi)作用域, foo對(duì)象將被自動(dòng)析構(gòu);如果之前沒(méi)有調(diào)用過(guò)delete ptr, ptr指向的對(duì)象將一直存在;如果delete ptr,析構(gòu)函數(shù)將被執(zhí)行。也可以將ptr指向的對(duì)象賦值給外層作用域的指針,此時(shí)有多個(gè)指針指向同一個(gè)變量 } }
從上面的代碼可以看出,對(duì)象沒(méi)有引用計(jì)數(shù)的情況下,編譯器和系統(tǒng)不敢隨便回收new出來(lái)的對(duì)象內(nèi)存:多個(gè)指針指向同一個(gè)對(duì)象,delete了對(duì)象可能會(huì)導(dǎo)致其他代碼崩潰;釋放內(nèi)存后,其他指針再delete會(huì)多次delete同一塊內(nèi)存,引發(fā)不可預(yù)知風(fēng)險(xiǎn)。
綜上,指針單例析構(gòu)函數(shù)沒(méi)有被調(diào)用的原因是: 自己new的對(duì)象,需要自己delete,別指望別人幫你正確調(diào)用析構(gòu)函數(shù)。
問(wèn)題的解決方案有以下幾種:
- 同StackOverflow上回答,改用變量方式持有單例對(duì)象。程序運(yùn)行結(jié)束前會(huì)銷毀所有變量,變量的析構(gòu)函數(shù)將被正確調(diào)用;
- 在main函數(shù)退出前delete單例。例如增加一個(gè)destroy的靜態(tài)成員函數(shù),將指針指向的對(duì)象銷毀;
- 使用auto_ptr/unique_ptr等智能指針。
如有其它解決方案,歡迎交流指正!
以上就是談?wù)凜++中的單例的詳細(xì)內(nèi)容,更多關(guān)于c++ 單例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
記錄一個(gè)C++在條件查詢時(shí)遇到的問(wèn)題(推薦)
這篇文章主要介紹了記錄一個(gè)C++在條件查詢時(shí)遇到的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01vc提示unexpected end of file found的原因分析
這篇文章主要介紹了vc提示unexpected end of file found的原因分析,給出了幾點(diǎn)常見(jiàn)錯(cuò)誤原因的分析,需要的朋友可以參考下2015-05-05C語(yǔ)言中經(jīng)socket接收數(shù)據(jù)的相關(guān)函數(shù)詳解
這篇文章主要介紹了C語(yǔ)言中經(jīng)socket接收數(shù)據(jù)的相關(guān)函數(shù)詳解,分別為recv()函數(shù)和recvfrom()函數(shù)以及recvmsg()函數(shù)的使用,需要的朋友可以參考下2015-09-09關(guān)于C/C++中static關(guān)鍵字的作用總結(jié)
以下是對(duì)C/C++中static關(guān)鍵字的作用進(jìn)行了總結(jié)介紹,需要的朋友可以過(guò)來(lái)參考下2013-09-09C++實(shí)現(xiàn)簡(jiǎn)單版圖書管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡(jiǎn)單版圖書管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06Inline Hook(ring3)的簡(jiǎn)單C++實(shí)現(xiàn)方法
這篇文章主要介紹了Inline Hook(ring3)的簡(jiǎn)單C++實(shí)現(xiàn)方法,需要的朋友可以參考下2014-08-08C/C++產(chǎn)生指定范圍和不定范圍隨機(jī)數(shù)的實(shí)例代碼
C/C++產(chǎn)生隨機(jī)數(shù)用到兩個(gè)函數(shù)rand() 和 srand(),這里介紹不指定范圍產(chǎn)生隨機(jī)數(shù)和指定范圍產(chǎn)生隨機(jī)數(shù)的方法代碼大家參考使用2013-11-11