Chinaunix首页 | 论坛 | 博客
  • 博客访问: 139612
  • 博文数量: 124
  • 博客积分: 70
  • 博客等级: 民兵
  • 技术积分: 1745
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-24 13:49
文章分类

全部博文(124)

文章存档

2011年(55)

2010年(14)

2009年(30)

2008年(25)

我的朋友

分类: C/C++

2010-09-27 16:36:48

近日阅读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::pInstance = NULL;

这样一个类,仅仅提供了SingletonHolder::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(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::bdestroyed = false;
Singleton::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的难题在于如何管理好退出

阅读(401) | 评论(0) | 转发(0) |
0

上一篇:学习boost::function

下一篇:leak dlg

给主人留下些什么吧!~~