之前写过一篇博客 《C++单例模式的模板基类》 http://blog.csdn.net/kentzhang_/article/details/48206821
但是,最近才发现实际 上 static T* Instance() 的实现是有一点bug 的,下面分析。
static T *Instance()
{
if(NULL != m_Instance)
{
return m_Instance;
}
m_Mutex.Lock();
if(NULL != m_Instance)
{
m_Mutex.UnLock();
return m_Instance;
}
m_Instance = new(std::nothrow) T();
m_Mutex.UnLock();
return m_Instance;
}
这种实现在多线程环境下,可能会有bug ,原因就在 m_Instance = new (std::nothrow) T() 这行代码。
这句代码实际上分三步执行:
1、分配内存
2、调用构造函数,初始化内存空间
3、将内存地址赋值给单例指针
代码可转化为如下形式:
T* p = newMemory(sizeof(T))
p->BaseSingleton::BaseSingleton()
m_Instance = p
编译器出于优化的目的,可能会将第一步和第三步合并,变成如下:
m_Instance = newMemory(sizeof(T))
m_Instance->BaseSingleton::BaseSingleton()
那么在执完第一行代码时,有可能会切换到其他线程,其他线程如果调用 Instance()函数,由于m_Insatnce已经赋值,将返回这个指针,但是
指针指向的内存并未初始化,所以会造成不可预知的错误。
在Boost库中,有一种很好的单例实现模式,我做了一点修改,就是引用改成指针。代码如下:
#ifndef BASESINGLETON_H
#define BASESINGLETON_H
/**
单例模式采用boost源码
由于boost单例返回的是引用,下面改成指针
*/
template<class T>
class BaseSingleton
{
public:
BaseSingleton(){}
private:
struct object_creator
{
// This constructor does nothing more than ensure that instance()
// is called before main() begins, thus creating the static
// T object before multithreading race issues can come up.
object_creator() { BaseSingleton<T>::Instance(); }
inline void do_nothing() const { }
};
static object_creator create_object;
//BaseSingleton();
public:
typedef T object_type;
// If, at any point (in user code), BaseSingleton<T>::instance()
// is called, then the following function is instantiated.
static object_type* Instance()
{
// This is the object that we return a reference to.
// It is guaranteed to be created before main() begins because of
// the next line.
static object_type obj;
// The following line does nothing else than force the instantiation
// of BaseSingleton<T>::create_object, whose constructor is
// called before main() begins.
create_object.do_nothing();
return &obj;
}
};
template <class T>
typename BaseSingleton<T>::object_creator
BaseSingleton<T>::create_object;
#endif // BASESINGLETON_H
我之前实现的单例模式中,必须是第一次手动调用Instance()才生成单例对象,这样就引入了多线程的问题。
而Boost实现方式,在单例类中定义一个内嵌的静态的结构体,这个结构体生成时调用自己的构造函数,构造函数中执行Instance()函数,由于单例类内的静态的结构体生成时,是在main()执行之前,所以巧妙地绕开了多线程的问题。