很多情况下,我们在开发项目的过程中,都希望自己运行的某个部件只有一个实例,
比如我们天天用QT开发界面,QTCreate里帮助菜单下的关于Qt Create菜单,弹出来的关于对话框,在QTCreate运行过程中,不论单击多少次,弹出的总是同一个对话框,这里的关于对话框就是一个单例模式实现的对象。
再比如说我们经常用的Windows下的任务管理器,无论打开多少次,同时同一个任务管理器对话框。
单例模式概述
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。
实现要点
单例模式的要点有三个;
①是某个类只能有一个实例;
②是它必须自行创建这个实例;
③是它必须自行向整个系统提供这个实例。
从具体实现角度来说,就是以下三点:
①是单例模式的类只提供私有的构造函数,
②是类定义中含有一个该类的静态私有对象,
③是该类提供了一个静态的共有的函数用于创建或获取它本身的静态私有对象。
注意事项
单例模式,赋值构造函数和拷贝构造函数都要声明为私有的,以便防止这类赋值的动作产生。
返回最好是返回引用,要不然用户会不小心删除掉指针的(如果非要返回指针,最好将析构函数声明为私有的)。
最简单实现
#include <iostream>
class Singleton
{
public :
static Singleton* GetInstance( ) // 获取对象单例的指针
{
if(Singleton::m_singleton == NULL) // 如果单例对象没有创建, 则将其创建
{
Singleton::m_singleton = new Singleton( );
}
return Singleton::m_singleton;
}
static void DestroyInstance( ) // 销毁单例对象的空间
{
if(Singleton::m_singleton != NULL)
{
delete Singleton::m_singleton;
Singleton::m_singleton = NULL;
}
}
private :
Singleton( ) // 构造函数[被保护]
{
}
Singleton(const Singleton &singleton) // 赋值构造函数[被保护]
{
}
static Singleton *m_singleton; // 指向单例对象的指针
};
Singleton* Singleton::m_singleton = NULL; // 指向单例对象的指针
int main()
{
Singleton *sp1 = Singleton::GetInstance( );
Singleton *sp2 = Singleton::GetInstance( );
std::cout <<(sp1 == sp2) <<std::endl; // 两个对象的地址是相同的
Singleton::DestroyInstance( );
return 0;
}
这是最简单,也是最普遍的实现方式,也是现在网上各个博客中记述的实现方式,但是,这种实现方式,有很多问题,
比如:没有考虑到多线程的问题,在多线程的情况下,就可能创建多个Singleton实例,以下版本是改善的版本。
考虑线程安全
#include <iostream>
class Singleton
{
public :
static Singleton* GetInstance( ) // 获取对象单例的指针
{
// 此处进行了两次m_Instance == NULL的判断,
// 是借鉴了Java的单例模式实现时,
// 使用的所谓的“双检锁”机制。
// 因为进行一次加锁和解锁是需要付出对应的代价的,
// 而进行两次判断,就可以避免多次加锁与解锁操作,
// 同时也保证了线程安全
if(Singleton::m_singleton == NULL) //
{
Lock( ); // 此处可以调用其他库的锁线程,或者自己实现一个
if(Singleton::m_singleton == NULL) // 如果单例对象没有创建, 则将其创建
{
Singleton::m_singleton = new Singleton( );
}
Unlock( ); // 此处可以调用其他库的锁线程,或者自己实现一个
}
return Singleton::m_singleton;
}
static void DestroyInstance( ) // 销毁单例对象的空间
{
if(Singleton::m_singleton != NULL)
{
delete Singleton::m_singleton;
Singleton::m_singleton = NULL;
}
}
private :
Singleton( ) // 构造函数[被保护]
{
}
Singleton(const Singleton &singleton) // 赋值构造函数[被保护]
{
}
~Singleton( ) // 析构函数
{
}
static Singleton *m_singleton; // 指向单例对象的指针
};
int main()
{
Singleton *sp1 = Singleton::GetInstance( );
Singleton *sp2 = Singleton::GetInstance( );
std::cout <<(sp1 == sp2) <<std::endl;
Singleton::DestroyInstance( );
return 0;
}
但是,这种实现方法在平时的项目开发中用的很好,也没有什么问题?
但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈;
为此,一种新的单例模式的实现也就出现了。
外部实例化
#include <iostream>
class Singleton
{
public :
static Singleton* GetInstancePoint( ) // 获取对象单例的指针
{
return const_cast<Singleton *>(Singleton::m_singleton);
}
static void DestroyInstance( ) // 销毁单例对象的空间
{
if(Singleton::m_singleton != NULL)
{
delete Singleton::m_singleton;
Singleton::m_singleton = NULL;
}
}
private :
Singleton( ) // 构造函数[被保护]
{
}
Singleton(const Singleton &singleton) // 赋值构造函数[被保护]
{
}
~Singleton( ) // 析构函数
{
}
static Singleton *m_singleton; // 指向单例对象的指针
};
///
Singleton* Singleton::m_singleton = new Singleton( ); // 指向单例对象的指针
///
int main()
{
Singleton *sp1 = Singleton::GetInstancePoint( );
Singleton *sp2 = Singleton::GetInstancePoint( );
std::cout <<(sp1 == sp2) <<std::endl;
Singleton::DestroyInstance( );
return 0;
}
内存泄漏问题
在上述的四种方法中,除了第四种没有使用new操作符实例化对象以外,其余三种都使用了;我们一般的编程观念是,new操作是需要和delete操作进行匹配的;是的,这种观念是正确的。在上述的实现中,是添加了一个DestoryInstance的static函数,这也是最简单,最普通的处理方法了;
但是,很多时候,我们是很容易忘记调用DestoryInstance函数,就像你忘记了调用delete操作一样。
#include <iostream>
class Singleton
{
public :
static Singleton* GetInstancePoint( ) // 获取对象单例的指针
{
return const_cast<Singleton *>(Singleton::m_singleton);
}
private :
Singleton( ) // 构造函数[被保护]
{
}
Singleton(const Singleton &singleton) // 赋值构造函数[被保护]
{
std::cout <<1221 <<std::endl;
}
~Singleton( )
{
}
static Singleton *m_singleton; // 指向单例对象的指针
class GC
{
public :
~GC( )
{
if (Singleton::m_singleton != NULL)
{
std::cout<< "Here destroy the m_singleton..." <<std::endl;
delete m_singleton;
m_singleton = NULL ;
}
}
static GC gc;
};
};
Singleton* Singleton::m_singleton = new Singleton( );
Singleton::GC Singleton::GC::gc;
int main()
{
Singleton *sp1 = Singleton::GetInstancePoint( );
Singleton *sp2 = Singleton::GetInstancePoint( );
std::cout <<(sp1 == sp2) <<std::endl;
return 0;
}
程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。那么这种实现方式的原理是什么呢?我剖析问题时,喜欢剖析到问题的根上去,绝不糊涂的停留在表面。由于程序在结束的时候,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,就像这些静态变量是全局变量一样。我们知道,静态变量和全局变量在内存中,都是存储在静态存储区的,所以在析构时,是同等对待的.