索引
- 静态化并不是单例 (Singleton) 模式
- 饿汉模式
- 懒汉模式 (堆栈-粗糙版)
- 懒汉模式 (局部静态变量-最佳版)
- 范例代码和注意事项 (最优实现)
- 扩展阅读
- 参考资料
我非常赞成合理的使用设计模式能让代码更容易理解和维护, 不过我自己除了简单的单例 (Singleton) 模式外, 其它都很少用 :-)
可耻的是, 直到前段时间拜读了C++ In Theory: The Singleton Pattern, Part I, 我才发现自己的单例 (Singleton) 模式写法还有改进空间.
文章作者 J. Nakamura 以 Log 日志类列举了单例 (Singleton) 模式的三种写法:
// log.h
#ifndef __LOG_H
#define __LOG_H
#include <list>
#include <string>
class Log {
public:
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
std::list<std::string> m_data;
};
#endif // __LOG_H
静态化并不是单例 (Singleton) 模式
初学者可能会犯的错误, 误以为把所有的成员变量和成员方法都用static修饰后, 就是单例模式了:
class Log {
public:
static void Write(char const *logline);
static bool SaveTo(char const *filename);
private:
static std::list<std::string> m_data;
};
In log.cpp we need to add
std::list<std::string> Log::m_data;
乍一看确实具备单例模式的很多条件, 不过它也有一些问题. 第一, 静态成员变量初始化顺序不依赖构造函数, 得看编译器心情的, 没法保证初始化顺序 (极端情况: 有ab两个成员对象,b需要把a作为初始化参数传入, 你的类就必须得要有构造函数, 并确保初始化顺序).
第二, 最严重的问题, 失去了面对对象的重要特性 — “多态”, 静态成员方法不可能是virtual的.Log类的子类没法享受 “多态” 带来的便利.
饿汉模式
饿汉模式是指单例实例在程序运行时被立即执行初始化:
class Log {
public:
static Log* Instance() {
return &m_pInstance;
}
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
static Log m_pInstance;
static std::list<std::string> m_data;
};
// in log.cpp we have to add
Log Log::m_pInstance;
这种模式的问题也很明显, 类现在是多态的, 但静态成员变量初始化顺序还是没保证.
还引起另外一个问题 (我之前碰到过的真实事件, 以后便一直采用下面提到的 “懒汉模式”): 有两个单例模式的类ASingleton和BSingleton, 某天你想在BSingleton的构造函数中使用ASingleton实例, 这就出问题了. 因为BSingletonm_pInstance 静态对象可能先ASingleton一步调用初始化构造函数, 结果ASingleton::Instance()返回的就是一个未初始化的内存区域, 程序还没跑就直接崩掉.
懒汉模式 (堆栈-粗糙版)
J. Nakamura 把它叫作 “Gamma Singleton”, 因为这是 Gamma 在他大名鼎鼎的 <<设计模式>> (<<Design Patterns>>)[Gamma]一书采用的方法. 称它为 “懒汉模式” 是因为单例实例只在第一次被使用时进行初始化:
class Log {
public:
static Log* Instance() {
if (!m_pInstance)
m_pInstance = new Log;
return m_pInstance;
}
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
static Log* m_pInstance;
static std::list<std::string> m_data;
};
// in log.cpp we have to add
Log* Log::m_pInstance = NULL;
Instance()只在第一次被调用时为m_pInstance分配内存并初始化. 嗯, 看上去所有的问题都解决了, 初始化顺序有保证, 多态也没问题.
不过细心的你可能已经发现了一个问题, 程序退出时, 析构函数没被执行. 这在某些设计不可靠的系统上会导致资源泄漏, 比如文件句柄, socket 连接, 内存等等. 幸好 Linux / Windows 2000/XP等常用系统都能在程序退出时自动释放占用的系统资源. 不过这仍然可能是个隐患, 至少 J. Nakamura 印象中, 有些系统是不会自动释放的.
对于这个问题, 比较土的解决方法是, 给每个 Singleton 类添加一个destructor()方法:
virtual bool destructor() {
// ... release resource
if (NULL!= m_pInstance) {
delete m_pInstance;
m_pInstance = NULL;
}
}
然后在程序退出时确保调用了每个 Singleton 类的destructor()方法, 这么做虽然可靠, 但却很是繁琐. 幸运的是, Meyers 大师有个更简便的方法.
懒汉模式 (局部静态变量-最佳版)
它也被称为 Meyers Singleton[Meyers]:
class Log {
public:
static Log& Instance() {
static Log theLog;
return theLog;
}
virtual void Write(char const *logline);
virtual bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
Log& operator=(Log const&); // assign op is hidden
static std::list<std::string> m_data;
};
在Instance()函数内定义局部静态变量的好处是,theLog“的构造函数只会在第一次调用“Instance()时被初始化, 达到了和 “堆栈版” 相同的动态初始化效果, 保证了成员变量和 Singleton 本身的初始化顺序.
它还有一个潜在的安全措施,Instance()返回的是对局部静态变量的引用, 如果返回的是指针,Instance()的调用者很可能会误认为他要检查指针的有效性, 并负责销毁. 构造函数和拷贝构造函数也私有化了, 这样类的使用者不能自行实例化.
另外, 多个不同的 Singleton 实例的析构顺序与构造顺序相反.
范例代码和注意事项 (最优实现)
把下面 C++ 代码片段中的Singleton替换成实际类名, 快速得到一个单例类:
class Singleton {
public:
static Singleton& Instance() {
static Singleton theSingleton;
return theSingleton;
}
/* more (non-static) functions here */
private:
Singleton(); // ctor hidden
Singleton(Singleton const&); // copy ctor hidden
Singleton& operator=(Singleton const&); // assign op. hidden
~Singleton(); // dtor hidden
};
Note
-
任意两个 Singleton 类的构造函数不能相互引用对方的实例, 否则会导致程序崩溃. 如:
ASingleton& ASingleton::Instance() {
const BSingleton& b = BSingleton::Instance();
static ASingleton theSingleton;
return theSingleton;
}
BSingleton& BSingleton::Instance() {
const ASingleton & b = ASingleton::Instance();
static BSingleton theSingleton;
return theSingleton;
}
-
在多线程的应用场合下必须小心使用. 如果唯一实例尚未创建时, 有两个线程同时调用创建方法, 且它们均没有检测到唯一实例的存在, 便会同时各自创建一个实例, 这样就有两个实例被构造出来, 从而违反了单例模式中实例唯一的原则. 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁 (虽然这样会降低效率).
-
多个 Singleton 实例相互引用的情况下, 需要谨慎处理析构函数. 如: 初始化顺序为ASingleton»BSingleton»CSingleton的三个 Singleton 类, 其中ASingletonBSingleton的析构函数调用了CSingleton实例的成员函数, 程序退出时,CSingleton 的析构函数将首先被调用, 导致实例无效, 那么后续ASingletonBSingleton的析构都将失败, 导致程序异常退出.
参考资料
[Gamma] |
Design Patterns: E.Gamma, R.Helm, R.Johnson and J.Vlissides. |
分享到:
相关推荐
C++完美实现Singleton模式
用VC实现的singleton 模式 在VS03,VC6.0下编译通过
c++ singleton单例模式
设计模式C++学习之单例模式(Singleton)
Java的Singleton模式代码(免资源分),你会发现Java的Singleton模式真的很有趣,原来程序还可以这样写。
C++设计模式课件12_Singleton_单件模式.pdf
Singleton模式: 确保一个类只有唯一的一个实例。 Singleton主要用于对象的创建,这意味着,如果某个类采用了Singleton模式,则在这个类被创建后,它将有且仅有一个实例可供访问。很多时候我们都会需要Singleton...
双重检测锁(Double-Checked Locking)实现的Singleton模式在多线程应用中有相当的价值。在ACE的实现中就大量使用ACE_Singleton模板类将普通类转换成具有Singleton行为的类。这种方式很好地消除了一些重复代码臭味,...
本文档,是利用C++来实现设计模式中的,单例模式,里面有内容说明和相关实例代码介绍
Java常用设计模式(SingleTon、FactoryMethod、AbstractFactory)
23种设计模式之三(创建型模式)Singleton模式
Head First 设计模式 (五) 单件模式(Singleton pattern) C++实现
1.3 Singleton 模式 1.4 Builder 模式 1.5 Prototype 模式 2 结构型模式 2.1 Bridge 模式 2.2 Adapter 模式 2.3 Decorator 模式 2.4 Composite 模式 2.5 Flyweight 模式 2.6 Facade 模式 2.7 Proxy 模式 3 行为模式 ...
最简单的设计模式学习Singleton模式
C++设计模式代码资源12_Singleton.zip
常见设计模式的解析和实现(C++),真的很经典,我也在学 常见设计模式的解析和实现(C++)之一-Factory模式 常见设计模式的解析和实现(C++)之二-Abstract ...常见设计模式的解析和实现(C++)之五-Singleton模式 .........
23种设计模式(Design Pattern)的C++实现范例,包括下面列出的各种模式,代码包含较详细注释。另外附上“设计模式迷你手册.chm” 供参考。 注:项目在 VS2008 下使用。 创建型: 抽象工厂模式(Abstract Factory) ...
描述设计模式之Singleton 模式的应、及举例说明了在JAVA中单利模式的具体应用。
java Singleton单例模式 java Singleton单例模式