單例模式,可以說設(shè)計模式中最常應(yīng)用的一種模式了,據(jù)說也是面試官最喜歡的題目。但是如果沒有學過設(shè)計模式的人,可能不會想到要去應(yīng)用單例模式,面對單例模式適用的情況,可能會優(yōu)先考慮使用全局或者靜態(tài)變量的方式,這樣比較簡單,也是沒學過設(shè)計模式的人所能想到的最簡單的方式了。
一般情況下,我們建立的一些類是屬于工具性質(zhì)的,基本不用存儲太多的跟自身有關(guān)的數(shù)據(jù),在這種情況下,每次都去new一個對象,即增加了開銷,也使得代碼更加臃腫。其實,我們只需要一個實例對象就可以。如果采用全局或者靜態(tài)變量的方式,會影響封裝性,難以保證別的代碼不會對全局變量造成影響。
考慮到這些需要,我們將默認的構(gòu)造函數(shù)聲明為私有的,這樣就不會被外部所new了,甚至可以將析構(gòu)函數(shù)也聲明為私有的,這樣就只有自己能夠刪除自己了。在Java和C#這樣純的面向?qū)ο蟮恼Z言中,單例模式非常好實現(xiàn),直接就可以在靜態(tài)區(qū)初始化instance,然后通過getInstance返回,這種就被稱為餓漢式單例類。也有些寫法是在getInstance中new instance然后返回,這種就被稱為懶漢式單例類,但這涉及到第一次getInstance的一個判斷問題。
下面的代碼只是表示一下,跟具體哪種語言沒有關(guān)系。
單線程中:
Singleton* getInstance()
{
if (instance == NULL)
instance = new Singleton();
return instance;
}
這樣就可以了,保證只取得了一個實例。但是在多線程的環(huán)境下卻不行了,因為很可能兩個線程同時運行到if (instance == NULL)這一句,導致可能會產(chǎn)生兩個實例。于是就要在代碼中加鎖。
Singleton* getInstance()
{
lock();
if (instance == NULL)
{
instance = new Singleton();
}
unlock();
return instance;
}
但這樣寫的話,會稍稍映像性能,因為每次判斷是否為空都需要被鎖定,如果有很多線程的話,就愛會造成大量線程的阻塞。于是出現(xiàn)了雙重鎖定。
Singleton* getInstance()
{
if (instance == NULL)
{
lock();
if (instance == NULL)
{
instance = new Singleton();
}
unlock();
}
return instance;
}
這樣只夠極低的幾率下,通過越過了if (instance == NULL)的線程才會有進入鎖定臨界區(qū)的可能性,這種幾率還是比較低的,不會阻塞太多的線程,但為了防止一個線程進入臨界區(qū)創(chuàng)建實例,另外的線程也進去臨界區(qū)創(chuàng)建實例,又加上了一道防御if (instance == NULL),這樣就確保不會重復創(chuàng)建了。
常用的場景單例模式常常與工廠模式結(jié)合使用,因為工廠只需要創(chuàng)建產(chǎn)品實例就可以了,在多線程的環(huán)境下也不會造成任何的沖突,因此只需要一個工廠實例就可以了。
優(yōu)點1.減少了時間和空間的開銷(new實例的開銷)。
2.提高了封裝性,使得外部不易改動實例。
缺點1.懶漢式是以時間換空間的方式。(上面使用的方式)
2.餓漢式是以空間換時間的方式。(下面使用的方式)
?
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
class Singleton{
public:
static Singleton* getInstance();
private:
Singleton();
//把復制構(gòu)造函數(shù)和=操作符也設(shè)為私有,防止被復制
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance;
};
#endif
#include "Singleton.h"
Singleton::Singleton(){
}
Singleton::Singleton(const Singleton&){
}
Singleton& Singleton::operator=(const Singleton&){
}
//在此處初始化
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance(){
return instance;
}
#include "Singleton.h"
#include
int main(){
Singleton* singleton1 = Singleton::getInstance();
Singleton* singleton2 = Singleton::getInstance();
if (singleton1 == singleton2)
fprintf(stderr,"singleton1 = singleton2n");
return 0;
}
?
以上使用的方式存在問題:只能實例化沒有參數(shù)的類型,其它帶參數(shù)的類型就不行了。
c++11 為我們提供了解決方案:可變模板參數(shù)
template
class Singleton { public: template static T* Instance(Args&&... args) { if(m_pInstance==nullptr) m_pInstance = new T(std::forward (args)...); return m_pInstance; } static T* GetInstance() { if (m_pInstance == nullptr) { ? const char * className = typeid(T).name(); ? QString fatal = "Singleton qFatal: <<"+QString(QLatin1String(className))+">> repeat init" throw std::logic_error("the instance is not init, please initialize the instance first");
} return m_pInstance; } static void DestroyInstance() { delete m_pInstance; m_pInstance = nullptr; } private: Singleton(void); virtual ~Singleton(void); Singleton(const Singleton&); Singleton& operator = (const Singleton&); private: static T* m_pInstance; }; template
T* Singleton ::m_pInstance = nullptr;
由于原來的接口中,單例對象的初始化和取值都是一個接口,可能會遭到誤用,更新之后,講初始化和取值分為兩個接口,單例的用法為:先初始化,后面取值,如果中途銷毀單例的話,需要重新取值。如果沒有初始化就取值則會拋出一個異常。
Multiton的實現(xiàn)
#include