轉自:我想有個長長的名字的博客,劍指offer 面試題2 Singleton模式 C++實現ios
題目:實現Singleton模式程序員
如下內容是我在看《劍指offer》的面試題2時,遇到的問題,由於書中使用C#實現,因此想用C++從新實現一下,Test方法不夠全,後續還要完善。C++實現過程主要參考:C++設計模式——單例模式。面試
代碼中的註釋通常是個人筆記,或一些發現。數據庫
PS: 感謝勤勞的慵懶君~~ @亦餘心之所向兮編程
缺點:多線程狀況下,每一個線程可能建立出不一樣的Singleton實例設計模式
// 劍指offer 面試題2 實現Singleton模式
安全
#include <iostream>
多線程
using namespace std;
函數
class Singleton
性能
{
public:
static Singleton* getInstance()
{
// 在後面的Singleton實例初始化時,若後面是new Singleton(),則此處沒必要new;(廢話)
// 若後面是賦值成NULL,則此處須要判斷,須要時new
// 注意!然而這兩種方式並不等價!後面的Singleton實例初始化時,new Singleton(),實際上是線程安全的,由於static初始化是在主函數main()以前,那麼後面的方法豈不是很麻煩。。。。這也是我測試的時候想到的
/*
if(m_pInstance == NULL)
{
m_pInstance = new Singleton();
}
*/
return m_pInstance;
}
static void destroyInstance()
{
if(m_pInstance != NULL)
{
delete m_pInstance;
m_pInstance = NULL;
} }
private:
Singleton(){}
static Singleton* m_pInstance;
};
// Singleton實例初始化
Singleton* Singleton::m_pInstance = new Singleton(); // 前面不能加static,會和類外全局static混淆
// 單線程獲取屢次實例
void Test1(){
// 預期結果:兩個實例指針指向的地址相同
Singleton* singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
Singleton* singletonObj2 = Singleton::getInstance();
cout << singletonObj2 << endl;
Singleton::destroyInstance();
}
int main(){
Test1();
return 0;
}
解法1是最簡單,也是最廣泛的實現方式,也是如今網上各個博客中記述的實現方式,可是,這種實現方式,有不少問題,好比:沒有考慮到多線程的問題,在多線程的狀況下,就可能建立多個Singleton實例,如下版本是改善的版本。
注意:下面的代碼涉及互斥鎖以及多線程測試,使用了C++11的多線程庫,std::thread,,std::mutex,請使用支持C++11多線程的編譯器,並確認開啓了C++11的編譯選項,具體方法見:http://blog.csdn.net/huhaijing/article/details/51753085
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
static mutex m_mutex; // 互斥量
Singleton(){}
static Singleton* m_pInstance;
public:
static Singleton* getInstance(){
if(m_pInstance == NULL){
m_mutex.lock(); // 使用C++11中的多線程庫
if(m_pInstance == NULL){ // 兩次判斷是否爲NULL的雙重檢查
m_pInstance = new Singleton();
}
m_mutex.unlock();
}
return m_pInstance;
}
static void destroyInstance(){
if(m_pInstance != NULL){
delete m_pInstance;
m_pInstance = NULL;
}
}
};
Singleton* Singleton::m_pInstance = NULL; // 因此說直接new 多好啊,能夠省去Lock/Unlock的時間
mutex Singleton::m_mutex;
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多個進程得到單例
void Test1(){
// 預期結果,打印出相同的地址,之間可能缺失換行符,也屬正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
int main(){
Test1();
Singleton::destroyInstance();
return 0;
}
此處進行了兩次m_pInstance == NULL的判斷,是借鑑了Java的單例模式實現時,使用的所謂的「雙檢鎖」機制。由於進行一次加鎖和解鎖是須要付出對應的代價的,而進行兩次判斷,就能夠避免屢次加鎖與解鎖操做,同時也保證了線程安全。可是,若是進行大數據的操做,加鎖操做將成爲一個性能的瓶頸;爲此,一種新的單例模式的實現也就出現了。
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
static const Singleton* m_pInstance;
public:
static Singleton* getInstance(){
return const_cast<Singleton *>(m_pInstance); // 去掉「const」特性
// 注意!若該函數的返回值改成const static型,則此處沒必要進行const_cast靜態轉換
// 因此該函數能夠改成:
/*
const static Singleton* getInstance(){
return m_pInstance;
}
*/
}
static void destroyInstance(){
if(m_pInstance != NULL){
delete m_pInstance;
m_pInstance = NULL;
}
}
};
const Singleton* Singleton::m_pInstance = new Singleton(); // 利用const只能定義一次,不能再次修改的特性,static繼續保持類內只有一個實例
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多個進程得到單例
void Test1(){
// 預期結果,打印出相同的地址,之間可能缺失換行符,也屬正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
int main(){
Test1();
Singleton::destroyInstance();
return 0;
}
由於靜態初始化在程序開始時,也就是進入主函數以前,由主線程以單線程方式完成了初始化,因此靜態初始化實例保證了線程安全性。在性能要求比較高時,就可使用這種方式,從而避免頻繁的加鎖和解鎖形成的資源浪費。因爲上述三種實現,都要考慮到實例的銷燬,關於實例的銷燬,待會在分析。
PS:該方法不能人爲控制單例實例的銷燬
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
public:
static Singleton* getInstance(){
static Singleton m_pInstance; // 注意,聲明在該函數內
return &m_pInstance;
}
};
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多個進程得到單例
void Test1(){
// 預期結果,打印出相同的地址,之間可能缺失換行符,也屬正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
// 單個進程得到屢次實例
void Test2(){
// 預期結果,打印出相同的地址,之間換行符分隔
print_singleton_instance();
print_singleton_instance();
}
int main(){
cout << "Test1 begins: " << endl;
Test1();
cout << "Test2 begins: " << endl;
Test2();
return 0;
}
以上就是四種主流的單例模式的實現方式。
在上述的四種方法中,除了第四種沒有使用new操做符實例化對象之外,其他三種都使用了;
咱們通常的編程觀念是,new操做是須要和delete操做進行匹配的;是的,這種觀念是正確的。在上述的實現中,是添加了一個destoryInstance的static函數,這也是最簡單,最普通的處理方法了;可是,不少時候,咱們是很容易忘記調用destoryInstance函數,就像你忘記了調用delete操做同樣。因爲怕忘記delete操做,因此就有了智能指針;那麼,在單例模型中,沒有「智能單例」,該怎麼辦?怎麼辦?
在實際項目中,特別是客戶端開發,實際上是不在意這個實例的銷燬的。由於,全局就這麼一個變量,全局都要用,它的生命週期伴隨着軟件的生命週期,軟件結束了,它也就天然而然的結束了,由於一個程序關閉以後,它會釋放它佔用的內存資源的,因此,也就沒有所謂的內存泄漏了。
可是,有如下狀況,是必須須要進行實例銷燬的:
在代碼實現部分的第四種方法能知足第二個條件,可是沒法知足第一個條件。好了,接下來,就介紹一種方法,這種方法也是我從網上學習而來的,代碼實現以下:
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* m_pInstance;
// **重點在這**
class GC // 相似Java的垃圾回收器
{
public:
~GC(){
// 能夠在這裏釋放全部想要釋放的資源,好比數據庫鏈接,文件句柄……等等。
if(m_pInstance != NULL){
cout << "GC: will delete resource !" << endl;
delete m_pInstance;
m_pInstance = NULL;
}
};
};
// 內部類的實例
static GC gc;
public:
static Singleton* getInstance(){
return m_pInstance;
}
};
Singleton* Singleton::m_pInstance = new Singleton();
Singleton::GC Singleton::gc;
void print_instance(){
Singleton* obj1 = Singleton::getInstance();
cout << obj1 << endl;
}
// 多線程獲取單例
void Test1(){
// 預期輸出:相同的地址,中間可能缺失換行符,屬於正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_instance));
}
for(auto& thr : threads){
thr.join();
}
}
// 單線程獲取單例
void Test2(){
// 預期輸出:相同的地址,換行符分隔
print_instance();
print_instance();
print_instance();
print_instance();
print_instance();
}
int main()
{
cout << "Test1 begins: " << endl;
cout << "預期輸出:相同的地址,中間能夠缺失換行(每次運行結果的排列格式一般不同)。" << endl;
Test1();
cout << "Test2 begins: " << endl;
cout << "預期輸出:相同的地址,每行一個。" << endl;
Test2();
return 0;
}
在程序運行結束時,系統會調用Singleton的靜態成員GC的析構函數,該析構函數會進行資源的釋放,而這種資源的釋放方式是在程序員「不知道」的狀況下進行的,而程序員不用特別的去關心,使用單例模式的代碼時,沒必要關心資源的釋放。
那麼這種實現方式的原理是什麼呢?因爲程序在結束的時候,系統會自動析構全部的全局變量,系統也會析構全部類的靜態成員變量,由於靜態變量和全局變量在內存中,都是存儲在靜態存儲區的,全部靜態存儲區的變量都會被釋放。
因爲此處使用了一個內部GC類,而該類的做用就是用來釋放資源,而這種使用技巧在C++中是普遍存在的,參見《C++中的RAII機制》。
運行結果: