singleton 读后感
时间:2010-09-27 来源:debianlm
近日阅读c++设计新思维,读到singleton的时候,不禁拍案叫绝,想不到一个单件竟然有如此多的陷阱,因此,为了防止自己以后忘记,将新的写了下来。
一般来说,一个singleton大致想一下可能会这么写,暂时不考虑mt.
template< typename T>
class SingletonHolder
{
public:
static T* instance()
{
if(!pInstance)
{
s_pInstance = new T();
}
return s_pInstance;
}
private:
singletonHolder();
SingletonHolder(const singletonHolder& rhs);
private:
static T* s_pInstance;
};
// in some cpp
Singletonholder<myclass>::pInstance = NULL;
这样一个类,仅仅提供了SingletonHolder<t>::instance() 阻止了其它的c++方式,
的构造。
那么进一步来说,这个类还有那些可以提高的地方,还是不考虑mt
1. 由于对于所有可能使用singleton的类来说,它都必须在某个cpp文件中,初始化这个成员变量. 即时实际不用到,当然这个缺点可以忽略,不过需要探讨
因此将此实现修改为如下...
template< typename T>
class SingletonHolder
{
public:
static T* instance()
{
static T obj;
return &obj;
}
private:
singletonHolder();
SingletonHolder(const singletonHolder& rhs);
};
这样做,那么根据c++的方式,如果某个T,它没有被真正的使用,那么是不会有任何资源被占用。
同时instance 函数内的判断语句也被省略了。
接下来继续分析。。。
现在的缺点是什么....
要知道现在的缺点,必须明白这种local static 的含义。。。
即刚才获得的好处不是免费的。。
如果要有伪代码那么刚才的instance内的实际情况或许如下。。。
static T* instance()
{
extern void destroysingleton();
extern void constructsingleton(void* mem);
static bool binit = false;
static char buf[sizeof(T)];
if(!binit)
{
constructsingleton(buf);
_atexit(destroysingleton);
binit = true;
}
return static_cast<T *>(buf);
}
这儿的atexit 实际上构建了一个exit stack,即lifo 方式。
也就是说这些local static variable的销毁方式是,谁先早创建,谁先晚销毁。
看到这儿,问题就来了。。。。
即如果存在多个singleton,它们之间还有关联,比如A需要使用B,而B可能是一个基本服务,那么就有可能出现,在
B销毁后,A还想用B的情况发生。
那么实际上我自己也在想,如果不进行刚才这个价值不大的优化,还是采用file scope 的静态变量,该如何呢?
实际上如果那样的话,问题变为, 如果T握有了network connection, gdi handle等资源,难道一定要等到进程退出才有os负责释放?
也就是说还是面临一个退出问题.
由于刚才那个小优化最后,也要面临这个问题,因此我们从研究角度,还是从那个继续往下说。。。
刚才那个情况,成为dead reference, 即引用一个不存在的对象。。。
有一种成为phoenix singleton , 具体来说,它能够在singleton销毁后,被再次创建....
那么这儿用到了c++的一种特性,即静态对象的内存在整个程序lifetime中会被保留,即使它已经被回收了)
因此修改过后的代码变为:
template< typename T>
class SingletonHolder
{
public:
static T* instance()
{
if(bdestroyed)
{
OnDead();
}
else
{
OnCreate();
}
return pinstance;
}
void OnCreate()
{
static T obj;
pinstance = &obj;
}
void OnDead()
{
// 在原地址上重新分配内存, 这样对于外部来说,相当于什么都没有变化
pinstance = new(pinstance) T;
}
private:
singletonHolder();
SingletonHolder(const singletonHolder& rhs);
private:
static bool bdestroyed;
static T* pinstance;
};
Singleton<T>::bdestroyed = false;
Singleton<T>::pinstance = NULL;
讲到这儿,关于singleton的很多角落都已经有了,我们再来看多线程的问题。
这儿讲述的是关于一个双循环检测和volatile的问题
static T* instance()
{
locker locker(s_locker);
if(pinstance)
{
...
}
..
}
这种做法将肯定能够解决多线程问题,不过代价大。。。
换一下。。。
static T* instance()
{
if(pinstance)
{
locker locker(s_locker);
..
}
..
}
这个还是有问题的,呵呵,原因不讲了。。。
static T* instance()
{
if(pinstance)
{
locker locker(s_locker);
if(pinstance)
{
...
}
..
}
..
}
这个吗? 貌似没有问题了,不过它还有问题,问题出在pinstance 必须被声明为volatile ,否则编译器在优化的时候可能会将第二个if优化掉
最后一个问题是single的依赖或者关联问题,比如我就是想设定几个singleton对象之间的退出先后关系。。
通过之前的描述发现,不管是用local static,还是pinstance 都有这个问题。。。
这个问题的解决办法,简单来说就是存在一个singletonmgr,它允许将singleton注册,同时由注册者指定生命时间,比如1-n,越大表示越后退出
然后在程序退出时候,通过统一注册的同一个函数,对singletonmgr按照寿命值,先后回收它们。。
至于loki本身采用的plolicy模式,比如它将
lifetime(none, phoeixsingleton, widthlifecount, nodestroy) , createmode ( local static , heap) , threadingmodel (singlethread, locker)
那么更加优美,不过这个这儿不描述了。。。
通过这个可以看到,一个貌似简单的singleton在不同做法时候能够碰到很多情况;
同时也总结为,singleton的难题在于如何管理好退出