何为单例模式,在《设计模式》一书中,其定义如下:保证一个类仅有一个实例,并提供一个访问他的全局访问点。其思想也很简单,为了防止有多个实例的出现,所以必须在类内构造一个实例,并且将其构造函数访问权限标记为private或protected。最后,为了提供一个全局访问点,必须在类内构造一个共有static函数,返回在类内部唯一构造的实例,UML图如下:
代码实现:
实例一:
class Singleton
{
public:
static Singleton *GetInstance(){
if (m_instance == NULL){
m_instance = new Singleton();
}
return m_instance;
}
static void destory_instance(){
if (m_instance != NULL){
delete m_instance;
m_instance = NULL;
}
}
int get_test(){
return m_test;
}
private:
Singleton(){ m_test = 10; }
static Singleton *m_instance;
int m_test;
};
Singleton *Singleton::m_instance = NULL;
int _tmain(int argc, _TCHAR* argv[])
{
Singleton *sig = Singleton::GetInstance();
std::cout << sig->get_test() << std::endl;
sig->destory_instance();
return 0;
}
这是最简单的一种实现方式,但他是有缺陷的,是非线程安全的。考虑一个问题:如果两个线程在首次构建对象是同时检测到m_instance为NULL,此时两个线程则会同时构造一个对象,这便产生了错误。
实例二:
std::mutex mtx;
class Singleton
{
public:
static Singleton *GetInstance(){
if (m_instance == NULL){
mtx.lock();
std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
if (m_instance == NULL){
m_instance = new Singleton();
}
}
return m_instance;
}
static void destory_instance(){
if (m_instance != NULL){
delete m_instance;
m_instance = NULL;
}
}
int get_test(){
return m_test;
}
private:
Singleton(){ m_test = 10; }
static Singleton *m_instance;
int m_test;
};
Singleton *Singleton::m_instance = NULL;
int _tmain(int argc, _TCHAR* argv[])
{
Singleton *sig = Singleton::GetInstance();
std::cout << sig->get_test() << std::endl;
sig->destory_instance();
return 0;
}
这个线程安全类借鉴了java的单例模式实现,使用所谓的”双检锁”机制。这种实现方式虽然保证了线程安全,但有个问题,即枷锁和解锁是需要耗费时间和空间资源的,如果进行大数据操作,此种实现方式就会到时性能下降。
实例三:
class Singleton
{
public:
static Singleton *GetInstance(){
return const_cast<Singleton *>(m_instance);
}
static void destory_instance(){
if (m_instance != NULL){
delete m_instance;
m_instance = NULL;
}
}
int get_test(){
return m_test;
}
private:
Singleton(){ m_test = 10; }
static const Singleton *m_instance;
int m_test;
};
const SingLeton *SingLeton::m_instance = new SingLeton();
int _tmain(int argc, _TCHAR* argv[])
{
Singleton *sig = Singleton::GetInstance();
std::cout << sig->get_test() << std::endl;
sig->destory_instance();
return 0;
}
上面这种实现方式,是单例模式两种实现方式的一种:“饿汉模式”,即在类构造时就将对象创建完成。还有此种方式不需要加锁解锁,因为他本身就是线程安全的。原因很简单因为你在类构造是就已经将对象创建完成,以后无论哪个线程调用类的静态方法都只是返回一个已经创建好的对象,故是线程安全的。但这种实现方式也有不好之处,就是不符合RAII设计原则,即不能自动负责对象的创建与销毁,人难免有忘记调用析构函数的时候,此时便会发生内存泄漏。当然这个不会产生内存泄漏,因为这个在程序关闭时便会将所占有的内存资源释放掉,故不会产生内存泄漏,但是当你所创建的对象有文件锁。文件句柄、数据库链接时,这些事不会随着程序关闭而释放掉的资源,故必须手动释放。所以创建一个符合RAII设计原则的类还是有必要的。
实例四:
class Singleton
{
public:
static Singleton *get_instance(){
return m_instance;
}
int get_test(){
return m_test;
}
private:
static Singleton *m_instance;
Singleton(){ m_test = 10; }
int m_test;
class GC{
public:
~GC(){
if (m_instance != NULL){
std::cout << "delete this" << std::endl;
delete m_instance;
m_instance = NULL;
}
}
};
static GC gc;
};
Singleton *Singleton::m_instance = new Singleton();
Singleton::GC Singleton::gc;
int _tmain(int argc, _TCHAR *args[])
{
Singleton *sing = Singleton::get_instance();
std::cout << sing->get_test() << std::endl;
getchar();
return 0;
}
在程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。由于程序在结束的时候,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,就像这些静态变量是全局变量一样。我们知道,静态变量和全局变量在内存中,都是存储在静态存储区的,所以在析构时,是同等对待的。