关于Singleton Template与于static member initialization问题

来源:互联网 发布:pplive网络电视下载 编辑:程序博客网 时间:2024/06/16 11:55

今天写singleton template碰到的问题,就是用template实现singleton模板,很容易就写好了一个基于static member lazy initialization的singleton,大概就是如下的代码段(没认真检查过,不知道有没有错):

 

 

template <typename T> class Singleton

{

private:

 

Singleton(const Singleton<T> &){};

 

Singleton& operator=(const Singleton<T> &){};

 

static T* m_instance;

 

 

protected:

Singleton(){};

public:

virtual ~Singleton(){};

 

static inline T& getSingleton()

{

return *getSingletonPtr();

}

 

static inline T* getSingletonPtr()

if(!m_instance)

m_instance = new T();

return m_instance;

}

 

static inline void Release()

if(m_instance)

delete m_instance;

m_instance = null;

}

 

};

 

然后用子类继承它,子类就成了单例,通过getSingletonPtr()获得指向实例的指针,通过Release释放,一切都中规中矩。需要在子类.cpp文件中函数体外写上

template<>

T Singleton<T>::m_instance = 0;

避免m_instance 一开始有些未知初值。

然后在搜索相关问题的时候发现:static member往往会带来线程安全问题,具体可以参考一篇文章:

http://blog.csdn.net/schnell/archive/2005/03/15/320373.aspx

 

该文阐述了这个问题,并给出了解决方法。它也是实现一个singleton,不过是普通的实现而不是用模板,差别在于模板需要有模板类参数信息,那么他的初始化就只能子类中完成,即通过

template<>

derivedclass Singleton<derivedclass >

语句,编译器才知道怎么初始化你的成员。

 

该文主要内容,实质上是涉及到static member lazy initialization和early initialization的对比。lazy initialization的好处是在需要时才初始化,也就是运行到该代码段时,而不是main函数一开始就对static member进行自动初始化操作。这可以避免代码没有运行,却自动初始化的浪费内存现象,这点在初始化占用大量内存的时候很重要。但是同时lazy initialization带来内存安全隐患,即使使用线程锁也不能避免thread unsafe的缺陷(见该文)。而early initialization可以保证在main函数运行之初,第一条语句尚未运行之前就进行初始化操作,保证了thread safe,代价是该static member必被自动实例化,可能因此浪费了内存。

写了一个early initialization 的版本,实现如下:

 

 

template <typename T> class Singleton

{

/*相同部分略*/

static T m_instance;

 

 

static inline T& getSingleton()

{

return *m_instance;

}

 

static inline T* getSingletonPtr()

return &m_instance;

}

 

};

 

 

这种实现呢,要求在所有singleton的子类都必须要在.cpp文件中函数体外完成对Singleton<T>::m_instance的实例化,即:

template<>

derivedclass Singleton<derivedclass >::m_instance;

这样m_instance在main函数运行之初就会初始化。

 

singleton还有另一种写法,大概实现如下:

 

template <typename T> class Singleton

{

/*其他略*/

static inline T* getSingletonPtr()

static T m_instance;

return &m_instance;

}

}

因为是函数内的局部static变量,因此也是在函数体首次执行时初始化,也是属于lazy initialization,同样是thread unsafe。

 

写的很乱,归纳一下:

 

1、全局static变量会在main函数运行函数体前完成初始化,这就是early initialization的本质,如果是模板函数,需要在函数体外给定模板类参数,那么也达成early initialization。通过static指针实现的lazy initialization也是全局static变量,但是只初始化了指针部分,堆中分配的内存尚未初始化。


2、刚发现的,全局变量初始化顺序是VS2010(其他编译器不知道)编译.cpp文件顺序的逆序,而cpp文件的编译顺序貌似是分文件夹编译的,在同文件夹内编译顺序暂时实验出来是CPP文件字母顺序的逆序,也就是说,全局变量初始化顺序,在其所在的文件夹内是按CPP文件字母顺序来的。为什么纠结这个无聊的问题呢?因为写代码时没注意的话很容易写出编译依赖的代码,如果变量初始化顺序跟逻辑上的依赖性无关,而是决定于文件字母顺序的话,那么可想而知这样写出来的代码是非常不安全的。所以当发觉变量初始化顺序跟逻辑上的依赖性无关时,我马上回头改掉了有初始化依赖的代码,并且决定记住这个错误:)


3、局部static变量是在运行期执行到该函数体时初始化,带来thread unsafe问题。

 

先到这里吧~

 

原创粉丝点击