#模板单例实现
本文参考 [用模板实现单例模式](http://blog.csdn.net/jnu_simba/article/details/9398465 "用模板实现单例模式") ,并且在此基础上做了适当的改进,使其支持更多功能。
#ifndef Singleton_h__
#define Singleton_h__
template<class T>
class Singletion
{
public:
static T* GetInstance()
{
static T m_Instance;
return &m_Instance;
}
private:
Singletion(){};
virtual ~Singletion(){};
Singletion(const Singletion& ){};
Singletion& operator=(const Singletion&){};
};
endif // Singleton_h__
/*
具体使用方法如下:
// 定义具体单例
class CCenterImp
{
public: // 构造函数要为public,不能为private和protect
CCenterImp(); // 在构造函数中进行必要的初始化
~CCenterImp (); // 在析构函数中进行资源的释放
private:
// Method
void print();
// Variable
}
}
typedef Singletion<CCenterImp> CCenter;
// 使用单例方法
CCenter::GetInstance()->print();
注意,要以typedef的方法来定义单例具体名称,具体单例CCenterImp的构造函数中初始化相关资源,在析构函数中释放相关资源。
提出问题1:为什么不用双重double的方式?
回答:双重double检查外加互斥锁的方式可以在满足获取要求,但什么时候释放申请的内存不好控制,以下是改进版双重检测
if (NULL == m_pInstance)
{
// Lock相关
if (NULL == m_pInstance)
{
T* temp = new T(); // 先new一个示例出来赋给临时变量
m_pInstance = temp; // 在赋值给真实的变量
}
}
return m_pInstance;
如果不需要做成模板复用的话,可以在此单例子中在增加一个内部嵌套静态类,在其析构函数中释放这里申请的内存,但如果要做成模板类的话,这个内嵌类就没法实现了,因此放弃这种方式。
思考2:对于new出一个单例的方式,如何放置释放处理函数。
回答: 参考网上的处理,有atexit函数,此函数是<stdlib.h>中的函数,可以注册一系列函数,用于在main函数退出后执行,在单例实例不多的情况下,可以考虑使用,但一定要注意它自身的限制,ISO C规定,一个进程在退出时,最多可以注册32个atexit函数,这些函数依次被exit函数所调用,如果要做成模板单例,供其他模块使用,就会有数量上的限制。
思考3:对于new出来的实例,尝试保存它自身以及相关析构函数,在系统销毁时,统一调用一次。
回答:今天曾尝试着将new出来的实例地址以及它对应的析构函数地址存放在map中,存放在另一个单例中,等系统快要退出时统一显示调用调用。这里有2个问题,一个是多个不同的单例实例,只能以void*的形式保存其指针,在准备调用delete时,指针保存的类信息丢失了,无法调用到其析构函数。 网上有利用汇编技巧的方法获取析构函数的地址,参考链接:http://www.cnblogs.com/findumars/p/3746869.html, 我尝试了下,执行到析构函数时发生崩溃了,尝试了多种方式,还是不行,此路不通。
思考4:单例类可以在外部new一个出来或者在本地定义一个局部变量吗?
回答:因为要构造一个单例类,所以这个单例类的构造函数一定不能是private或者是protect类型的,但是,这样一类,就可以直接在外部定义这样一个类了,不管你是在单例的GetInstance里面new出来,还是 局部静态变量出来,都需要构造函数。
思考5: 局部静态变量的单例模式需不需要加锁?
回答: 参考此篇文章,http://blog.csdn.net/yichigo/article/details/37878117, 如果加锁保护,那么以后每次调用GetInstance都会有锁,如果不加锁,如果构造函数中执行的时间较长,在多线程环境下,可能会这个变量构造到一半,就被另一个线程拿过去用了。为了平衡这两种情况,在程序中,对于此单例有可能有多线程访问的情况下,在程序启动初始化时,显示调用一个CCenter::GetInstance(),手动触发初始化操作后,然后开启多线程,这样,既保证了在多线程工作时,此单例肯定是初始化完成了,在获取时,也不需要加锁,快速直接。备注:局部静态变量的线程安全性,在C++0x上的编译器可以保证,在linux平台下的gcc编译器可以保证,在低版本的VC下不保证,因此,不要把这个变化的部分推给编译器。在程序初始化时,显示调用一次,确保构造完成后,再开启多线程相关工作,我们是可以掌控这种情况的。
思考6: 对于上文提出的,静态局部变量的方式,有一点要注意,模板类里面的构造、析构、拷贝构造、赋值要设置private,实现类里面的构造要设置为public,不能设置为protect和private。这里有一个小缺陷,用户可以定义或者new CCenterImp 类,但不能定义或者new CCenter类。整个单例对外表现的对象为CCenter类,但是,CCenterImp只能定义在头文件里面,这样,别人看到了就有误用的可能,想来想去,也没想到什么好的方法,只能通过注释文字说明来提示使用者,使用CCenter类,而不要直接使用CenterImp类。
好了,关于单例的思考和讨论汇总为这么多,以后在使用时,如果需要复用,就用上述提到的模板类,如果不需要复用,简简单单一个局部静态变量加上手动初始化,可确保万事大吉。