問題描述
某些類型(例如前面介紹的工廠類)在一個系統中只能出現一份實例。《More Effective C++》條款26詳細的討論一些可能的方法。那麼,單件模式給我們帶來了什麼呢?
單件模式
如圖所示,單件模式提供的解決方案是:讓類自己保存自己的一份實例(m_instance),提供一個訪問該實例的方法(Instance()),並限制其他途徑來創建另外一個實例。
討論
1)。線程安全性: 由於m_instance是靜態成員變量,在多線程環境下,如果兩個線程同時判斷“if(m_instance == 0)”,那麼兩個線程都可能滿足條件,從而創建出兩份實例。然而m_instance只可能保留某個線程的創建結果,顯然這裏出現了內存泄露。更奇怪的事情是:某個線程兩次調用Instance()方法返回的實例指針可能完全不同!我們不能接受內存泄露,更不能忍受這種不確定性,因此需要確保Instance()方法被多線程互斥地訪問。
2)。如何創建Singletone的子類,並且讓Instance()返回子類的實例對象? 下面的代碼似乎可以解決這個問題,但是很明顯的是:添加任何一個Singleton的子類例如SingletonChildB,Instance()函數都必須修改,這違反了Open/Close原則。一個改進的方式是:在Singleton中實現一個單件註冊表,Instance()函數通過查表來返回一個已經註冊了的單件。
class SingletonChildA;
class Singleton
{
public:
static Singleton* Instance(const char* child_name)
{
if(m_instance == 0)
{
if(strcmp(child_name, "SingletonChildA") == 0)
{
m_instance = new SingletonChildA();
}
else if(...)
{
}
else
{
m_instance = new Singleton();
}
}
return m_instance;
}
private:
static Singleton* m_instance;
};
單件註冊表的支持代碼如下:
class Singleton
{
public:
static void Register(const char* child_name, Singleton*);
static Singleton* Instance()
{
const char* singletonName = getenv("SINGLETON");
return m_maps[singletonName];
}
private:
static map<char*, Singleton*> m_maps;
};
class SingletoneChildA : public Singletone
{
public:
SingletoneChildA()
{
Singleton::Register("SingletonChildA", this);
}
....
};
static SingletonChildA theSingletonChildA;
上述代碼中值得一提的是:Instance()接口不能通過參數傳遞Singleton子類的名字。想象一下,如果在任何調用Instance()的地方都傳遞一個名字,一旦需求發生變化,需要修改另外一個子類的名字的時候,代碼需要到處修改!考慮到某個時刻特定類型的Singleton實例只需要一個,通過一個環境變量來配置Singleton子類的名字,把可能的修改限制到系統啓動過程的配置環境變量的語句。