談談C++中的單例
寫C++的時候用到單例,於是很自然的寫出如下的程式碼:
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; }
程式碼的本意:靜態成員函式getInstance獲取單例指標,並且在解構函式中做一些收尾工作。
執行程式碼後發現解構函式死活不執行,難道一個單例模式都能寫錯?反覆確認,沒發現問題所在,於是上萬能的StackOverflow上找原因。正好有夥計有同樣的疑惑,有哥們給出了一個可行的方案。根據其答案修改程式碼如下:
namespace tlanyan { class Foo { private: Foo() {} // other members public: static Foo& getInstance() { static Foo _instance; return _instance; } ~Foo() { // clean codes } // other members and codes }; }
對比前一段程式碼,主要改動是移除了靜態指標成員,改用函式內的靜態成員。由於_instance是函式內的靜態成員,在首次呼叫時被初始化(感謝無參建構函式),之後呼叫將略過初始化而執行後續程式碼;函式返回例項的引用,故而每次呼叫得到的是同一個物件,達到了單例的目的;程式執行結束後,例項的解構函式被自動呼叫,解構函式中的程式碼正確執行。
問題解決了,但什麼原因造成第一段單例程式碼的解構函式不執行呢?
這是由於C++持有物件的方式造成的(或者說C++允許程式設計師手動控制記憶體引起)。Java/PHP等帶有回收機制的語言,持有物件的方式是通過指標,程式設計師申請物件後會自動分配記憶體,系統負責跟蹤和回收無用的物件和存在。C++允許開發人員以變數的方式持有物件,例如:Foo foo [= Foo(args)]
Foo* foo = new Foo(args)
。這種方式分配的物件,需要開發人員手動管理。如果不執行delete,物件和分配的記憶體將一直存在,直到程式退出後,才由作業系統回收。如下程式碼可說明這點:
namespace tlanyan { void foo() { Foo foo; // 宣告變數,編譯器會自動初始化變數 Foo* ptr = new Foo(); // 宣告物件指標,同時為物件開闢記憶體 // awesome codes return; // 離開作用域, foo物件將被自動析構;如果之前沒有呼叫過delete ptr, ptr指向的物件將一直存在;如果delete ptr,解構函式將被執行。也可以將ptr指向的物件賦值給外層作用域的指標,此時有多個指標指向同一個變數 } }
從上面的程式碼可以看出,物件沒有引用計數的情況下,編譯器和系統不敢隨便回收new出來的物件記憶體:多個指標指向同一個物件,delete了物件可能會導致其他程式碼崩潰;釋放記憶體後,其他指標再delete會多次delete同一塊記憶體,引發不可預知風險。
綜上,指標單例解構函式沒有被呼叫的原因是: 自己new的物件,需要自己delete,別指望別人幫你正確呼叫解構函式。
問題的解決方案有以下幾種:
- 同StackOverflow上回答,改用變數方式持有單例物件。程式執行結束前會銷燬所有變數,變數的解構函式將被正確呼叫;
- 在main函式退出前delete單例。例如增加一個destroy的靜態成員函式,將指標指向的物件銷燬;
- 使用auto_ptr/unique_ptr等智慧指標。
如有其它解決方案,歡迎交流指正!
以上就是談談C++中的單例的詳細內容,更多關於c++ 單例的資料請關注我們其它相關文章!