在很多开发者印象中,单例模式可能是23种设计模式中最简单的一个,我觉得那只是说对了一半,如果不考虑多线程,
的确如此,但是一旦要在多线程中运用,那么从如下的代码示例例子当中,它涉及到很多编译器,多线程,C++语言
标准等方面的内容。下面是我经过各方参考而来的单例模式的各种实现以及优缺点,最后如何选择单例模式实现
方式。
// 标准实现-教科书实现
class CSingletonl
{
public:
//全局访问点由于是静态函数所以只能通过类名调用
static CSingletonl *GetInstance()
{
if(NULL == m_instance)
{
m_instance = new CSingletonl();
}
return m_instance;
}
//内存释放函数
static void ReleaseInstance()
{
if(NULL != m_instance)
{
delete m_instance;
m_instance = NULL;
}
}
//测试打印函数
// void Print()
// {
// printf("print out CSingletonl\n");
// }
private:
//假设该类有很多成员函数,需要1秒钟进行初始化
//使用Sleep(1000)进行模拟
CSingletonl()
{
// printf("CSingletonl Begin Construct\n");
// ::Sleep(1000);//让出CPU时间进入睡眠状态,不加有可能都是第一个线程在打印。其他线程竞争不过它得不到执行时间
//有点线程切换的作用意思
// printf("CSingletonl End Construct\n");
}
virtual ~CSingletonl()
{
// printf("CSingletonl Destruct\n");
}
private:
CSingletonl(const CSingletonl&){};
CSingletonl& operator=(const CSingletonl&){};
private:
static CSingletonl * m_instance;
}
CSingletonl *CSingletonl::m_instance=NULL;
//懒汉模式 多线程不安全 ,单线程OK 多线程测试如下
unsigned int __stdcall thread(void *)
{
printf("current Thread ID= %d\n",::GetCurrentThread());
CSingletonl::GetInstance()->Print();
// 我们不能在线程中调用ReleaseInstance()函数会导致程序崩溃。
//多线程下我们无法是释放CSingletonl的内存
//CSingletonl::ReleaseInstance
return 0;
}
void TestMultThread()
{
//我们创建三个线程
for(int i = 0;i<3;i++)
{
//调用<process.h>中的线程调用函数,该函数比Win32 CreateThread(当我们使用EixtThread时候会导致暂时性内存泄漏)更安全
//为什么,请参考win32核心编程中的相关描述
//如果要使用多线程,尽量使用该函数
uintptr_t t= ::_beginthreadex(NULL,0,thread,NULL ,0,NULL);//(微软使用了线程局部内存存储技术解决了)
::CloseHandle((HANDLE)t);
}
}
int main()
{
TestMultThread();
getchar();
return 0
}
//3个线程+main 一共4个线程
//我们在每个辅助线程里面调用了GetInstance()静态方法,由于每个线程回掉函数速度非常快,导致每个线程在判断NULL == m_instance
//时,都返回True(都等于NULL),从而导致每个线程回掉函数都会创建一个CSingleton1对象并返回纸箱该对象的指针。
// 我们根本没有办法进行CSingleton1的内存释放,因为在多线程中,我们根本不知道是创建了1个 2 个 还是3个CSingleton1的实例
// 如果线程里面释放会 报Double free错误。程序崩溃。
//Meyers Singleton Pattern 实现 这个实现比较流行高效(里面不需要判断了) 简单 也是懒汉模式 也是在第一次
//调用才会实例化。
//使用对象而不是指针分配内存,因此自动回掉析构函数(推出main之前会自动掉析构函数),从而不会导致内存泄漏。
//再多线程下的确能够保证有且只有一个实例产生。 多个线程虽然有且只有一个实例也不一定是真正意义多线程安全的。
//但是在多线程在并不是真正意义上的线程安全的实现?为什么?
//
class CSingleton2
{
public:
//单例模式使用局部静态变量方式
//从而使之延迟到调用时实例化
static CSingleton2 & GetInstance()
{
static CSingleton2 sg;//局部静态变量
return sg; //返回引用
}
void print()
{
printf("print Singleton2 count = %d\n",m_count);
}
private:
int m_count;//这个就是为了看一下它的不同之处而特意弄得变量
CSingleton2()
{
printf("begine construct Singleton2 count=%d\n",m_count);
::Sleep(1000);
m_count=100;
printf("begine construct Singleton2 count=%d\n",m_count);
}
~CSingleton2()
{
printf("CSingleton2 Destruct\n");
}
private:
//防止拷贝构造以及赋值操作
CSingletonl(const CSingletonl&){};
CSingletonl& operator=(const CSingletonl&){};
};
//演示 我直接复制上面的
unsigned int __stdcall thread(void *)
{
printf("current Thread ID= %d\n",::GetCurrentThread());
// 我们不能在线程中调用ReleaseInstance()函数会导致程序崩溃。
//多线程下我们无法是释放CSingletonl的内存
//CSingletonl::ReleaseInstance
CSingleton2::GetInstance()->Print();//Meyers的版本
return 0;
}
void TestMultThread()
{
//我们创建三个线程
for(int i = 0;i<3;i++)
{
//调用<process.h>中的线程调用函数,该函数比Win32 CreateThread(当我们使用EixtThread时候会导致暂时性内存泄漏)更安全
//为什么,请参考win32核心编程中的相关描述
//如果要使用多线程,尽量使用该函数
uintptr_t t= ::_beginthreadex(NULL,0,thread,NULL ,0,NULL);//(微软使用了线程局部内存存储技术解决了)
::CloseHandle((HANDLE)t);
}
}
int main()
{
TestMultThread();
getchar();
return 0
}
//3个线程 分成2内存分配、成员变量的初始化。有可能内存分配了但是被另一个线程切换走了。所以我们代码中增加了m_count变量
//让我们看到第二个线程等于m_count等于0.第三个线程打印确等100了。就是因为第三个线程切换到的时候m_cout被第一个线程初始化了变量。
//证明: 虽然保证了有且只有一个实例的产生,但是不能保证实例的产生过程中内存的分配与成员变量的初始化具有原子性(就是只有完成了以后才能初始化)。
//这也是Meyers最大的问题。
//最后一步 main退出时候会掉析构函数。
/*原因:
这是因为C++中构造函数并不是线程安全的。
C++中的构造函数简单来说分为两步
第一步:内存分配
第二步:初始化成员变量
由于多线程的关系,可能当我们在分配内存好了以后,还没来得急初始化成员变量,就进行了线程切换,另外一个线程拿到所有权后,
由于内存已经分配好了,但是变量的初始化还没进行,因此打印成员变量的相关值会发生不一致现象。
结论:Meyers 方式虽然能够保证在多线程中产生唯一的实例,但是不能确报成员变量的值是否正确。
*/
//基础版本 --多线程安全 懒汉模式
//实现一个线程安全的单例模式 ---懒汉模式 按需内存分配 模板实现 自动内存析构
//高效 线程安全、线程同步分为内核模式和用户模式,用户模式级别比内核同步高,而用户效率最高的是
//InterlockedXXXX系列API。 这实际上也是一个Double - check Locking 实现的单例模式
// 是传统的的Double-Checked-Locking 的变异版本
//何谓Double-Checked-Locking啊? 二次锁定线程
/*形如 下
if(NULL == m_instance)
Lock();
if(NULL == m_instance)
m_instance =new CSingleton//这时候才真正的new
unlock();
return m_instance;
//实际就是使用锁住关键段
*/
//基础版本
template <class T>
class CSingletonPtr
{
private:
static trl::shared_ptr<T> m_instancePtr;
private://封闭 赋值 与拷贝构造
CSingletonPtr(const CSingletonPtr &){};
CSingletonPtr& operator=(const CSingletonPtr&){};
public: //构造与析构 为public 必须为Public否则编译就出错 ,至于为什么?
CSingletonPtr()
{
printf("SingletonPtr Begin Construct\n");
::Sleep(1000);
printf("SingletonPtr End Construct\n");
}
virtual ~ CSingletonPtr()
{
printf("SingletonPtr Destruct\n");
}
static trl::shared_ptr<T> GetInstance()
{
static volatile long lock=0;
/*
if(lock == 0)
lock =1; 这段代码会有什么问题呢?
如果这段代码是在多线程情况下,而lock是跨线程共享的变量(static 意味着跨线程的
因为生命期长其他也能找到它)
那么可能存在着这样的情况,线程1正在比较lock ==0 ,而线程2 正在赋值 lock=1,这样
就会产生问题(本来等于0 以判断等于1了),我们必须要避免这种情况那么我们可以使用
interlockedCompareExchange函数,如果一个线程调用该函数,那么该函数会锁定lock的
内存地址,其他线程就不能同时访问从而实现多线程环境下的线程互斥。
*/
int ret =0;
/*使用InterlockedCompareExchange
1、 锁定的变量必须是volatile类型 ,导致编译器不能发生优化(Release版本可能导致程序优化
为了让变量具有更加访问性,调整相关顺序以利于CPU高速缓存器的使用,可能会把变量放到
而不是放到寄存器中去,而不是写到内存中去,直到函数结束在写回内存去。这对多线程具
有灾难性的,因为InterlockedCompareExchange 必须要确保每一次的读必须要从内存地址
里面读取,写也必须写到地址里面去。所以为了这种情况不发生必须使用Volatile)
2、类型是32位的 long 、int、unsigned long 只能锁定4个字节
InterlockedCompareExchange 的用法:
第一个参数:变量 的地址
第三个参数:值 ---就是如果第一个参数的值等于第三个参数值得话
第二个参数:值 ,将第二个参数赋值给变量。//注意只有相等了才赋值
返回值: 返回变量原来的值。
假设第一次lock =0;掉了这个函数以后lock变成1,并返回以前的值0;
*/
if(ret = ::InterlockedCompareExchange(&lock,1,0) !=0)
//返回不等0 进入函数体(除了第一次,以后每次都返回1 不等于0)
{
while(lock !=2 )
::Sleep(0);
return m_instancePtr;
}
//只有第一次才会走下面这里
/*
假如有3个线程 第一个线程拿到所有权 执行到InterlockedCompareExchange ,突然第二个线程也拿到所有权也掉
InterlockedCompareExchange,由于第一个线程在锁定 返回0了。第一个线程就会往下走创建相关的实例。
但是第二个线程拿到所有权后进入if里面 停在while 循环不等于2的话 Sleep(0)就是告诉操作系统你分配我
当前使用的线程的时间片如10毫秒 我只发了1毫秒,剩下的9毫秒对我来说是浪费,我告诉系统我不要剩下的9毫秒
你让其他线程进行相关的访问我就睡觉了,啥时候醒呢?是下一次轮到我的时候我才醒。由此可见他一直在睡觉
轮询当中,而第一个线程new 分配内存 初始化好了 就把lock =2 了。
也就是第一个调用InterlockedCompareExchange的函数才能够返回0。才能够进行类的初始化分配内存,好了以后将
lock的值等于2 ,其他线程就一直在等待等于2.只有发现等于2了,意味着其他地方已经创建好了实例,就直接
return出去了.这样就保证每一次调用GetInstance 有且返回一个实例并且保证new 内存分配以及成员变量初始化都
是以原子的方式完成。那么也只有第一个调用InterlockedCompareExchange 的线程才会设置lock等于2,其他地方的
线程永远都不会设置,就确定了确保一旦对象创建后lock永远都等于2,以后就不会创建具有线程安全性。
*/
trl::shared_ptr<T> temp(new T());
m_instancePtr =temp;
lock=2;
printf("内存分配并初始化成员变量完成\n");
return m_instancePtr;
}
};
template <class T>
trl::shared_ptr<T> CSingletonPtr<T>::m_instancePtr;//模板静态成员变量初始化
class Manager:public CSingletonPtr<Manager>//CSingletonPtr 是一个基类版本仅仅实现创建实例
// 如果实例化继承 Manager 模板参数 Manager 类可能会增加很多成员 这里增加了 m_count
{
public:
Manager()
{
printf("Manager Begin Construct\n");
::Sleep(1000);
printf("Manager End Construct\n");
}
~Manager()
printf("SingletonPtr Destruct\n");
}
public:
void Print()
{
printf("Hi,I'm Manager m_count = %d\n",m_count++);
}
private:
int m_count;
};
unsigned int __stdcall thread(void*)
{
printf("current Thread ID= %d\n",::GetCurrentThreadId());
/*
CSingleton::GetInstance()->Print();
我们不能在线程中调用ReleaseInstance()函数会导致程序崩溃。
在多线程下我们无法释放CSingletonl的内存
CSingleton::ReleaseInstance()
CSingleton2::GetInstance().Print();
*///还用上面测试代码以上注释掉 就改一下这里 Manager::GetInstance()->Print();
Manager::GetInstance()->Print();
return 0;
}
void TestMultThread()
{
//我们创建三个线程
for(int i;i<3;i++)
{
//调用<process.h> 中线程创建函数,该函数比Win32 CreateThread 更安全
//为什么,请参考win32核心编程中的相关描述
//如果要使用多线程,尽量使用该函数
uintptr_t t = ::_beginthreadex(NULL,0,thread,NULL,0,NULL);
::CloseHandle((HANDLE)t);
}
}
//基础版本的测试
int main()
{
TestMultThread();
getchar();
return 0;
}
/*基础版测试代码说明
current Thread ID =9188
SingletonPtr Begin Construct
第一个辅助线程创建并调用构造函数,但是构造还没有完成
current Thread ID =8176
current Thread ID =8412
第一个辅助线程还未完成构造函数调用,但是此时第二个第三个线程获得线程所有权,但是由于此时构造
函数被第一个线程锁定着 , 使得第二第三个线程无法调用Print函数。只有第一个线程将lock设置为2第
二第三才会解锁跳出。
SingletonPtr End Construct
Manager Begin Construct
Manager End Construct
内存分配并初始化成员变量完成
这时候线程又切换到第一个辅助线程,它完成基类的构造函数调用,初始化基类成员变量,又初始化子类的
所有成员变量。
Hi,I‘m Manager m_cont =0;
Hi,I‘m Manager m_cont =1;
Hi,I‘m Manager m_cont =2;
目前基类和子类的内存分配和成员变量初始化完成了,无论哪个线程获得所有权,都使共享的Manager类的
成员变量m_count++输出,我们会看到m_count在多线程中递增显示的,说明是正确的。
Manager Destruct
SingletonPtr Destruct
但我们完成整个操作后,程序结果,由于使用shared _ptr只能指针,我们能够正确的,无误的析构内存
这样我们通过模板增加了通用性 通过原子锁加锁线程安全 通过shard_ptr内存的自动析构 。这样就
实现了一个基础版线程安全的单例模式
基础版存在的不足:
基类(模板类CSingletonPtr) 与子类Manager 的构造与析构都要求为Public 。这是因为我们使用了模板
单例的一个原则就是禁止构造函数和析构函数为Public,防止外部实例化,仅允许调用GetInstance()
等静态方法进行初始化。
由于使用模板技术,如果我们不将基类和子类的构造和析构函数设置为public级别,模板实例化导致编译
器报错。因为如果使用Private protected模板的话编译器就会报无法访问的错误。(貌似这就是模板的原理)。
那我们有什么方法 将够构造析构给封闭掉呢?其实是有方法的
*/
//1 、修正构构函数
// 1)将基类的CSingletonPtr 的构造函数为Protected访问级别
protected:
CSingletonPtr()
{
printf("SingletonPtr Begin Construct\n");
::Sleep(1000);
printf("SingletonPtr End Construct\n");
}
//2)将子类的Manager 构造函数也设置为Protected访问级别,并在继承的子类里面添加友元
friend class CSingletonPtr<Manager>;
/*基类就可以使用Manager的任何访问请求函数 因为是模板所以
CSingletonPtr<Manager>; 所以加上<Manager>的类名 这样CSingletonPtr 就能够调用子类Manager的
构造函数
*/
protected:
Manager()
{
printf("SingletonPtr Begin Construct\n");
::Sleep(1000);
printf("SingletonPtr End Construct\n");
}
//3) 在上述代码设定后,我们会发现对于构造函数,通过子类授权给基类的方式,我们能够很顺利的通过编译,
//代码正确的运行
/*这样我们解决了防止第三方调用Manager的构造函数,Manager类的构造函数只允许在GetInstance()静态方法中被调用
我们会发现,对于析构函数,它依旧是public访问级别的,为什么不让析构函数也声明为protected 级别呢?
因为由于我们使用std::trl::shared_ptr(与boost::shared_ptr基本一致),Manager类的析构并不是在
CSingletonPtr进行的而是委托给了shared_ptr,而Manager授权给的是其基类,所以如果我们将析构函数设置
为protected级别的,shard——ptr会找不到,编译器会报错。
那么我们第一个反应就是我们继续在Manager中授权给shared_ptr.但是没成功,可能是由于share_ptr实现的机制导致
不能成功。难道我们真的没有办法将析构函数修正为受保护级别吗?
山穷水尽疑无路,柳暗花明又一村!
强大的shared_ptr的删除器的出现解决了我们的问题!shared_ptr可以定制析构删除
*/
//2 、修正析构函数
//1) 在基类CSingletonPtr中声明并实现一个访问级别为Private的嵌套类Deleter ,代表一个删除器 重载函数调用操作符,
//该删除器类实际上是一个仿函数对象。
template<class T>
class CSingletonPtr
{
private:
class Deleter{ // 定义一个类 private 外部不能访问的额
public:
void operator()(T *p)//重载函数调用操作符
{
delete p;
}
};
}
//2)在 GetInstance()静态函数中增加删除器设置代码,见图2红色部分。
//一旦我们设置好删除器,那么在shared_ptr析构时不会直接调用delete而是调用删除器。
// 这样我们就将子类T的析构函数隐藏起来,不被外部调用。
int ret =0;
if(ret = ::InterlockedCompareExchange(&lock,1,0) != 0)
{
while(lock !=2)
::Sleep(0);
return m_singletonPtr.get();
}
trl::shared_ptr<T> temp(new T().T::Deleter());//构造里面掉Deleter的构造函数 这样shared_ptr析构时候不会直接调用
//delete操作符号,而是 调用删除器。这样我们就将子类T的析构函数隐藏起来,不被外部调用.
m_singletonPtr =temp;
lock =2;
//3)每一个继承自CSingletonPtr的子类也将构造函数声明为proteced访问级别,但不需要声明授权友元类
//(对于析构函数由于我们使用了删除器所以不需要友元类)。
//通过上述步骤,我们将基类和子类的构造函数都声明为受保护级别,以防止外部调用。
// 这样整个单例子类的生命周期都由shared_ptr控制。
//3、 修正GetInstance()静态方法:
/*
对于基础版本的GetInstance()静态方法返回的是trl::shared_ptr<T>结构,这样导致每次调用GetInstance()
都会使shared _ptr的引用计数加1 并调用shared_ptr的拷贝构造函数。在调用完成GetInstance()->print方法后,
又将临时产生的shared_ptr 对象引用计数减1,这样对效率有非常大的影响。
我们要避免 让它返回一个引用
*/
static T& GetInstance()
{
static volatile long lock =0;
int ret =0;
if(ret = ::InterlockedCompareExchange(&lock,1,0) != 0)
{
while(lock !=2)
::Sleep(0);
return *m_singletonPtr.get();//修正这里 返回 取值
}
trl::shared_ptr<T> temp(new T().T::Deleter());
m_singletonPtr =temp;
lock =2;
printf("内存分配并初始化成员变量完成\n");
return *m_instancePtr.get(); // 同样这里也返回取值 返回引用
}
// 这样就完成了智能指针相关的修正。返回一个原始智能指针的一个引用。以增加我们的效率
//那么进一步,我们使用编译器提供的本质函数, 算是第四个更正也在GetInstance里面 有点偏离我们的目标了
//但是为了增加效率使用编译器提供的本质函数 略微增加点效率
extern "C" __cdecl _InterlockedCompareExchange(long volatile * Dest,long Exchange,long Comp);
#pragma intrinasic(_InterlockedCompareExchange)//通知编译器我要使用这个函数
//编译器内部实现的函数基本都是汇编 都是编译器内部实现的_InterlockedCompareExchange
//内部会掉用编译器编译器实现的_InterlockedCompareExchange 意味着每次调用函数进栈 出栈。
static T& GetInstance()
{
static volatile long lock =0;
int ret =0;
if(ret = ::InterlockedCompareExchange(&lock,1,0) != 0)
//替换成带下划线的_InterlockedCompareExchange intrinasic函数
{
while(lock !=2)
::Sleep(0);
return *m_singletonPtr.get();//修正这里 返回 取值
}
trl::shared_ptr<T> temp(new T().T::Deleter());
m_singletonPtr =temp;
lock =2;
printf("内存分配并初始化成员变量完成\n");
return *m_instancePtr.get(); // 同样这里也返回取值 返回引用
}
/*线程安全的单例模式(修正版的)优缺点
1、优点:该实现是一个懒汉单例模式,意味着只有再第一次调用GetInstance的静态方法的时候才进行内存分配
通过模板和继承方式,获得了足够通用的能力
在创建单例实例的时候,具有线程安全性
通过智能指针的方式,防止内存泄漏。
具有相对的高效性。
2、缺点:肯定没有单线程版本的效率高
每个子类必须要授权基类(为了调用受保护的构造函数),我们可以写一个宏减少输入:
#define DECLARE_SINGLETON_CLASS(type) \
friend class CSingletonPtr<type>;
*/
饿汉类型单例模式实现
//饿汉模式意味着在主线程main函数代表主线程之前就对类进行内存分配和初始化,实现代码如下
template<class T>
class CEagerSingleton
{
public:
static T&GetInstance()
{
return sg;
}
protected:
CEagerSingleton()
{
printf("begin construct EngerSingleton\n");
::Sleep(1000);
printf("end construct EngerSingleton\n");
}
virtual ~CEagerSingleton()
{
printf("~EngerSingleton Destruct\n");
}
private:
//防止拷贝构造以及赋值操作
CEagerSingleton(const CEagerSingleton&){};
CEagerSingleton& operator=(const CEagerSingleton){};
static T sg;//声明为类的静态私有变量,编译器确保sg的实例化在主线程运行之前初始化完成。
};
template<class T>
T CEagerSingleton<T>::sg;//类的静态模板变量的初始化格式
//跟Myyes很像但不一样。
class EagerManager:public CEagerSingleton<EagerManager> //子类继承基类
{
friend class CEagerSingleton<EagerManager>;//授权基类调用子类的析构函数
protected:
EagerManager()
{
printf("EagerManager Begin Construct\n");
::Sleep(500);//延迟为了看到多线程下的竞争
m_count=0;
printf("EagerManager End Construct\n");
}
~EagerManager()
{
printf("EagerManager Destruct\n");
}
public:
void Print()
{
printf("Hi ,I'm EagerManager m_count =%d\n",m_count++);
}
private:
int m_count; //增加成员变量
};
//饿汉模式测试 也是直接复制测试代码 修改下就可以了
unsigned int __stdcall thread(void*)
{
printf("current Thread ID= %d\n",::GetCurrentThreadId());
/*
CSingleton::GetInstance()->Print();
我们不能在线程中调用ReleaseInstance()函数会导致程序崩溃。
在多线程下我们无法释放CSingletonl的内存
CSingleton::ReleaseInstance()
CSingleton2::GetInstance().Print();
Manager::GetInstance()->Print();
*///还用上面测试代码以上注释掉 就改一下这里 Manager::GetInstance()->Print();
EagerManager::GetInstance().Print();
return 0;
}
void TestMultThread()
{
//我们创建三个线程
for(int i;i<3;i++)
{
//调用<process.h> 中线程创建函数,该函数比Win32 CreateThread 更安全
//为什么,请参考win32核心编程中的相关描述
//如果要使用多线程,尽量使用该函数
uintptr_t t = ::_beginthreadex(NULL,0,thread,NULL,0,NULL);
::CloseHandle((HANDLE)t);
}
}
int main()
{
printf("欢迎进入主线程 id= %d\n",::GetCurrentThreadId());
//为了证明在调用前饿汉就初始化了所以打印一下主线程的ID号,如果在前面就初始化了就证明了再main前就实例化了。
TestMultThread();
getchar();
return 0;
}
/*测试输出说明
begine construct EagerSingleton
end construct EagerSingleton
EagerManager Begin Construct
EagerManager End Construct
打印以上句说明在进入主线程前已经完成了EagerSingleton对象的初始化工作,意味着主线程或3个辅助线程不会中断
EagerManger的内存分配和变量的初始化工作,单例的创建具有线程安全性
欢迎进入主线程 id=5288 此时进入主线程
current Thread ID= 3208
current Thread ID= 5984
Hi,I‘m EagerManager m_count =0;
Hi,I‘m EagerManager m_count =1;
这里说明主线程的2个辅助线程获得所有权,由于单例实例已经初始化好了,一次每个线程将共享单例实例的m_count
器加1.
current Thread ID =5958
Hi,I‘m EagerManager m_count=2
第三个辅助线程获得所有权,将共享的单例实例的m_count 加1
EagerManager Destruct
EagerSingleton Destruct
在退出主线程前,将单例对象 自动进行析构,防止内存泄漏
*/
//貌似一切都很完美 , 不需要线程同步 (不需要原子锁),内存不会泄漏,基于模板足够通用,速度肯定比其他实现更快,
//最重要的是代码量非常少,很容易实现.
//除了没有按需要进行分配外,一切都是如此之完美。
/*饿汉模式单例模式最隐晦的问题-平时碰不到除了代码写很多时候的确会出问题
SCott Meyers 在其<Effective C++>第三版的04条款,确定对象在使用前先被初始化有关的Non - Local static
变量初始化顺序的描述中举例子说明了该模式的缺陷。
1、Non - Local Static (非本地静态变量)变量的确在主线程前进行了初始化。
2、但C++标准对于不同的编译单元内的Non-Local Static 对象的初始化相关次序并没有明确定义,这是有原因的;决定
他们初始化顺序相当困难,根本无解。(.cpp文件编译后形成.Obj文件,代表一个编译单元),也就是你不知道.cpp文件的
编译顺序的,因此C++无法确定全局或Non - Local Static初始化顺序是怎么样的。
3、上述现象主要体现在,如果某一个编译单元内的某个Non-Local-Static 对象的初始化使用了另一个编译单元内的某
个Non-Local-Static 对象,它所用到的对象可能尚未被初始化。
很多数据库上面就会有问题,无法保证这个链接在其他之前就初始化了。简单的话,程序可控你可能可以使用。
因此Scott Meyers 提出了我们第三节中实现的单例模式(Meyers模式),我们发现在多线程下还是存在问题,
也就是说Meyers提出的在多线程下也是存在问题的。
关于这个隐晦的问题,所以要确保的话还是使用其他模式.
在我们的测试代码中很难重现,但是网上确实有很多文章在实际项目中遇到这个问题。
*/
//单例模式四种实现总结
/*
从编译器以及是否线程安全方面考虑:
1、如果你使用VC6编译器,请放弃设计模式。 (因为VC6并不是可靠的)
2、如果你整个程序是单线程的额,那么标准模式(教科书版本)或Meyers单例模式是你最佳选择。
3、如果你使用复合C++ ox标准的编译器的话,由于C++ ox标准规定:要求编译器保证内部静态变量的线程安全性。
(VC2010 以及以上版本。因此Meyers单例模式是你最佳选择)(Meyers 保证了就一个实例但是不保证内存
分配与变量初始化的原子性。这个在0x ++标准中已经指出来了。时代发展C++编译器也发展了)
4、如果你使用VC6以后,vc2010以下版本的编译器的话,并且需要线程安全,则使用第四节实现的Double-Checked-Locking
版本的单件模式。
5、尽量避免使用饿汉模式的单例模式,因为具有潜在,不可预测的风险。 编译顺序有关无法明确掌握它的规律。
//以上3种 各有适合地方 但对于第四种 饿汉模式。不建议使用 其实随着时代发展 微软公司Vista操作系统以及
以后win7 win8 增加了InitOne 机制 保证只允许创建一次 很多都是很多回掉函数,比C++更加底层。
*/
/*单例模式固定格式
从单例模式实现的角度来考虑:
1、总是避免第三方调用拷贝构造以及赋值操作符 private
2、总是避免第三方掉用构造函数 protected
3、尽量避免 第三方调用析构函数
4、总是需要一个静态方法用于全局访问
*/