Ogre 设计模式之Singleton

Ogre很多地方采用了单件模式,单件模式的好处在于维护某个类在程序中只有唯一的实例,实例可以在程序的命名空间中的如何地方被直接调用,这样就避免了对实例指针的传递。

LogManager ,ControllerManager, DyLibManager等都是使用的单例模式。

 

既然知道了单例模式的好处和Ogre的广泛用途,少年你肯定想知道Ogre的singleton是如果实现的吧。下面我们结合LogManager来对它研究一番。

 

单例模式大家都知道(PS : 不知道的少年可以参考《设计模式》一书),我们下面主要针对普通Singleton和Ogre的Singleton的相同点和不同点来进行一下分析

 

相同点:

 

1           维护某个类在程序中只有唯一的实例,都可以通过getSingleton() , getSingletonPtr()或instance()等方式来获得唯一的实例。

 

2           因为要用到不同的派生类中,一般把Singleton提出来用template加以实现,这样派生类直接继承Singleton,并实例化模板参数即可,如下:

 

template <typename T> class Singleton

class _OgreExport LogManager : public Singleton<LogManager>, public LogAlloc

 

 

其中的LogAlloc,这里暂时不展开来谈,等到分析Ogre内存分配的时候,再详细的说明。对于继承于Singleton的类的具体实现,我们要特化Singleton的static成员变量,对于LogManager

 

template<> LogManager* Singleton<LogManager>::msSingleton = 0; 

 

 

不同点:

 1.         普通的Singleton如(boost Singleton)构造函数为protected,程序调用instance的地方生成实例,在需要的地方直接调用instance或者getSingleton这类的函数即可:

 

// singleton.hpp

template <class T>

/*static*/ T &singleton<T>::instance()

{

    // function-local static to force this to work correctly at static

    // initialization time.

    static singleton<T> s_oT;

    return(s_oT);

}

 

MyClass::instance().doSomething();

 

 

而Ogre的Singleton有个特点在于它的构造函数是public的,初始化的而且是需要new的,如下:

 

mLogManager = OGRE_NEW LogManager();
mLogManager->createLog(logFileName, true, true);

 

 

但在之后调用的时候就直接getSingleton就可以了。

       我认为Ogre这样做的原因,是把最耗时间的初始化操作放在程序的开始,而等到真实渲染的时候,不必再为初始化instance而耗费时间,这个设计的缺点在于初始化的时间耗费比较多。

 

2.         Ogre使用单例比较特殊的地方,在于在每个单例里面,他都会重写,而重写的函数只调用基类Singleton的方法,如下:

static LogManager& getSingleton(void);
static LogManager* getSingletonPtr(void);

 

     这样做的好处在于把模板的操作限定在单个类的实现中,而编译器不需要编程时再跑到Singleton的头文件中,再对模板进行编译。当你如果dll中导出的单件时候,不是基于单个文件的模板类就会出问题,会出现链接错误的情况,经本人确认的确如此,如果在具体的单件中重写两个方法就会把static的实例化限制在单个编译单元中,而函数只需要调用基类的方法,这样可以避免link error。

 

3.         因为Ogre跨平台和支持多编译器的特性,在Singleton 的构造函数都会对编译器的版本做一次判断,如下:

        Singleton( void )

        {

            assert( !msSingleton );

#if defined( _MSC_VER ) && _MSC_VER < 1200    

            int offset = (int)(T*)1 - (int)(Singleton <T>*)(T*)1;

            msSingleton = (T*)((int)this + offset);

#else

         msSingleton = static_cast< T* >( this );

#endif

        }

 

       比较难理解的地方在于#if defined( _MSC_VER ) && _MSC_VER < 1200 这行的预编译判断,<1200 意味着 vc的版本小于 vc6.0 (囧 那么老的编译器还有人用吗) ,static的内存模型就会不同,需要做this指针做一次offset的调整,具体的内存模型目前上不太清楚,其实理解了感觉用处也不是特别大。

posted @ 2012-11-05 15:45  singmelody  阅读(835)  评论(0编辑  收藏  举报