2006年08月13日

C++ Templates: The Complete Guide

Amazon上的一个评价:You’ll laugh, you’ll cry, you’ll fall down…
enjoying…

2006年05月19日

       本文并非讲述Singleton模式,而仅仅是通过这个模式的实现来挖掘设计时应该考虑的问题和一些不常用的语言特性,并讨论这些语言特性给我们带来的好处。

 

 

在单件模式中,我们通过将类的构造函数定义为私有或保护来防止用户创建多个类的对象,而在类中提供一个静态函数返回一个全局唯一类对象来提供对唯一对象的访问,如:

Example 1

 

 

class Singleton

{

public:

     static Singleton & Instance()

     {

         return m_pInstance;

     }

 

 

protected:

     Singleton(){}

     ~Singleton(){}

 

 

private:

     static Singleton m_pInstance;

};

       这种方法的一个变种是在Instance函数中声明静态实例:

Example 2

 

 

     Singleton & Singleton::Instance()

     {

         static Singleton c_Instance;

         return c_Instance;

     }

       第二种方法的好处是在该类在第一次使用时才被创建出来。然而有一些编译器在类的构造和析构为私有时并不能正确处理该代码。这种方法另外的问题就是static不能带给我们足够的灵活性,很多情况下单件类事例的创建要依赖于其他类,或者这种创建本身就是一个工厂模式;还有一个很严重的问题就是对于static 对象的创建和销毁没有控制权,在Example 1中,对象在main函数之前被创建,在Example 2中,对象在第一次使用时被创建,两个例子中,对象都是在程序结束时被销毁(main之后),对象的创建和销毁都是由c++ runtime处理的,而c++标准对于创建和销毁的顺序没有规定,也就是当系统中有多个类似的对象时,我们不知道编译器会以什么顺序来创建和销毁它们,在一个有着复杂依赖关系的系统中,上述方法无疑是没有现实意义的。

       因此,我们使用指针来创建和存储单件:

Example 3

 

 

class Singleton

{

public:

     static Singleton * Instance()

     {

         if ( m_pInstance )

              return *m_pInstance;

         else

              return new Singleton();     // 此处可替换为更灵活的创建方式

     }

 

 

protected:

     Singleton(){}

     ~Singleton(){}

 

 

private:

     static Singleton * m_pInstance;

};

       为了提供对销毁对象的控制,我们可以提供一个静态的Destory方法来销毁对象:

Example 4

 

 

     void Singleton::Destroy()

     {

         if ( m_pInstance )

              delete m_pInstance;

     }

 

 

       在大型系统(如游戏)中,存在大量的单件类,为每一个类都书写上述的代码既不合算又不够专业,既然是相同的代码,我们有必要将其抽象出来,c++提供了一个有利的工具template

Example 5

 

 

template <class T>

class Singleton

{

public:

     static T * Instance()

     {

         if ( m_pInstance )

              return *m_pInstance;

         else

              return new T();

     }

 

 

protected:

     Singleton(){}

     ~Singleton(){}

 

 

private:

     static T * m_pInstance;

};

       在使用时,只要从该模版声明一个实例:

Example 6

     class SomeSingleton{}; // 你的想成为单件的类

 

 

     Singleton<SomeSingleton> s_MySingleton;

 

 

       这个模版虽然为我们省去了很多不必要的输入工作,s_MySingleton也的的确确是一个单件的对象,然而我们可以看到,这里的SomeSingleton类并不是一个单件,用户依然可以创建多个SomeSingleton的对象,事实上,这就算不上是一个单件模式了。

       问题的原因是模版Singleton是一个单件类,而我们的类不是,要使得我们的类成为一个单件,就要让我们的类is-a Singleton

Example 7

     class SomeSingleton : public Singleton<SomeSingleton>

{

protected:

     SomeSingleton(){};

};

    

       在这种派生中,基类是以子类实例化的模版,这种模式有一个特殊的名称,叫做:Curiously Recurring Template Pattern (CRTP),在网上看到有人译为“诡异而循环的模版模式”,别管译的是否恰当,这种方法的确是很诡异!

       在这里,SomeSingleton的基类是模版Singleton的一个实例,这个实例是用SomeSingleton来实现的,这种方法实际上给了我们一种在基类中控制派生类的能力,CRTP是一个较为复杂且庞大的概念,以我目前的水平还不能将其讲清楚。详细的介绍可以参见《thinking in c++》和《c++ template》。CRTP是一种惯用法,但它并非c++特有,CRTP提出者Jim Coplien曾用多种语言展示过CRTP

       我们回到对单件模版的讨论,此代码将无法通过编译,原因是在基类的Instance函数中访问了派生类的构造函数,而派生类的构造函数是保护的,因此,我们还需要做一下修改:

Example 8

     class SomeSingleton : public Singleton<SomeSingleton>

{

     friend Singleton<SomeSingleton>

protected:

     SomeSingleton(){};

};

       将基类声明为派生类的友元类,这样,在基类中就可以访问派生类的构造函数了。到此这个单件模版应该是可以满足需要了,然而仔细考虑后我们发觉让用户通过基类的指针或引用来访问我们的单件并不是件好事,也就是说is-a关系并不恰当(关于这个论断我还没有太深刻的感触,此处is-a关系关键的缺点是什么?)

       对于这个问题,有人提出了以下解决方案:

Example 9

     class SomeSingleton : private Singleton<SomeSingleton>

{

};

       这样就将继承关系从公有变成了私有,关于私有继承,有必要再要讨论一下。

我们以前在学习过私有继承时仅仅知道它使得基类中所有的成员都成为派生类的private成员,然而我认为这并不足以作为这个语言特性被设计出来的原因,实际上,私有继承有着更深层的含义:

       私有继承中派生类和基类之间的关系是私有的,也就是说我们不能看到一个派生类是从某个基类继承过来的,这样就和公有继承产生了本质的不同,我们知道公有继承是一种is-a关系,也就是我们可以把派生类当作一个基类来用。而在私有继承中,在派生类外部我们看不到继承关系,所以私有继承也就不再是一种is-a关系,而变成了一种has-a关系。事实上,编译器并不会将私有继承的派生类对象转型为基类对象。

       既然私有继承是一种has-a关系,那么私有继承和组合(composition)有什么相同和不同?其实私有继承和组合仅仅是has-a关系两种语法上的实现,在组合中,一般情况下(非友元)宿主类只能操纵组合类的公有成员,这符合对象封装的要求,耦合较小;而在私有继承中,由于继承关系的特点,派生类可以访问基类的共有和保护成员,然而这种权力的增加也就意味着责任的增加,因为这样使得它们之间的耦合更大一些,因此这一般仅作为一个短程的解决方案,另外,私有继承在某些情况下可以将派生类对象强制转型成基类对象来使用,而且利用私有继承,编译器把没有非静态成员变量的基类大小优化为0,而组合则不行。在一般情况下,使用组合可以很好的完成类结构的设计,对私有继承,还是要持谨慎的态度,关于私有继承的详细介绍,可参加网上的《C++箴言:谨慎使用私有继承》一文。

       另外一个有意思的问题就是保护派生,它基本和私有派生一样,所不同的是这种派生关系可以被派生类的派生类所看到,也就是基类的“孙类”可以访问到基类。这种派生关系比私有继承更不常用。俺也不是很懂,不敢多说了 :P 关于CRTP以及私有,《程序员》2001/8有一篇非常好的文章,网上有转载《CountingObjectsinC++》或《在 C++ 中计算对象个数》,另外CSDN社区关于此文也有一些讨论可以参考。

       回到我们的例子,这样一来,对于派生类的外部来说,就看不到派生类的继承关系,也就好像它不是从任何类派生出来一样。然而这里还有一个问题,就是我们单件要使用的Instance()函数,是在基类中定义的,虽然在基类中是公有的,但由于私有派生,在派生类中就变成了私有,这样我们就不能调用派生类的Instance()函数了。解决这个问题的方法是将Instance函数提升为公有:

Example 10

     class SomeSingleton : private Singleton<SomeSingleton>

{

public:

     using Singleton<SomeSingleton>::Instance;

protected:

     SomeSingleton();

};

       这里使用using关键字来改变基类中函数的访问控制,到此,我们的单件模版类就算近乎完美的完成了。

       然而像所有戏剧性的故事一样,在我们追求使用高级语言特性来解决问题的时候,往往忽略了原始而朴素的解决方案,在没有template的“黑暗的c年代”,曾经有一种大放光彩的技术,它的名字叫做宏:

       宏的作用远不止定义常量,看这个例子:

#define DEFINE_SINGLETON(cls)\
     private:\
          static cls * m_pInstance;\
     protected:\
          cls(){}\
     public:\
          static cls* Instance(){\
                if(!m_pInstance){\
                     m_pInstance = new cls();\
                }\
                return m_pInstance;\
          }

#define IMPLEMENT_SINGLETON(cls) \

cls * cls::m_pInstance = NULL;
假定你需要实现一个单件类YY,这样书写:
class YY {
DEFINE_SINGLETON(YY);
public:
//your interfaces here…
};
cpp文件中:
IMPLEMENT_SINGLETON(YY);
需要引入这个类的实例的时候,使用这样的语句:
YY* pYY = YY::Instance();

没有复杂的模版,没有诡异的CRTP,仅仅是#define,虽然这种实现的效果比起模版来说还是有些不足,然而其简洁易用,也算是很不错的解决方案了。《大规模c++程序设计》曾这样写,好的软件工程师与蹩脚的软件工程师的区别就是他知道什么时候停止。事实上对于好的设计的追求是永无止境的,而现实中我们需要的并非一定是最优解,在很多成功的软件中,其设计并不像我们想象中的那样优雅,恰到好处的设计,才是真是好而实用的设计。

 

 

参考资料:

1.       singleton/template problem: deriving and the access level, http://www.gamedev.net/community/forums/topic.asp?topic_id=298359

2.       c++中计算对象个数《程序员》2001/8

3.       《面向对象的游戏开发》

4.       一种自动的singleton工具,《游戏编程精粹1

2006年03月30日

Inside the C++ Object ModelBook Cover

Inside the c++ object model 应该是不用多说了,光看Amazon上的9个五星也知道其分量了,好在经过不太长的预约,借到了侯捷先生的译本:
“C++成山似海的书籍堆中,这一本不是婴幼儿奶粉,也不是较大婴儿奶粉,它是成人专用的低脂高钙特殊奶粉”
没有读过此书的C++er们,想长大吗,准备好,该喝奶了。

Large Scale C++ Software Design 却没有得到如此好的评价,在此忍不住要为其辩护一下,此书是继古老的圣经 K&R 的 The C Programming Language之后唯一让我感到如此震撼和如此强烈的共鸣的,在读The C Programming Language时,由于还对c甚至编程一无所知,因此书中几乎每句话都能给人大量的信息;而如今读Large Scale c++ Software Design,似乎又回到了那个懵懂的时代,面对着一个崭新的世界,每一句都让人回味。读此书,联想到实际编码的经历,时刻都让你有拍案叫绝的冲动,原来那些百思不解的问题背后有如此深厚的知识体系,犹如在一望无际的沙漠中发现了传说中的宝藏,怎么能让人不兴奋呢!唯一可惜的问题是中文翻译不敢恭维,而原版只找到电子版。


这两本书放到一起再恰当不过了,大体上一看,前者是对c++机制向更深处的挖掘,让人了解底层实现;而后者是对c++软件项目架构设计上的探讨,是宏观的拓展。然而,二者其实都是对语言实现底层问题的说明和解决,没有对底层知识的深刻理解,根本无法设计出好的宏观架构,这不正是我们伟大自然界一贯的规律么,从微观到宏观,是何其的相似!

2006年03月25日

Masters of Doom

DOOM启示录

……我不需要走到大峡谷边才能感叹于造物的神奇,我可以就坐在屋里,看光线反射的样子……
John Carmack

2006年03月24日

Object-Oriented Game Development

面向对象的游戏开发,个人认为很不错的书。
如果你已经写过很多代码并感觉一切不过如此,那么,可以读它了。

MSRA的论文,一个交互系统的实现。

效果看起来很不错。

论文看起来好像是个算法集成?系统还是要求不小的用户工作?

2006年03月23日

AI for Game Developers

很好读,GAME AI 入门,推荐

2006年03月20日

没有搞到此书光盘,搜了一下,发现Amazon上关于此书的评论似乎不怎么样,都说光盘内容和书上的代码不一致,也就不找了。

书中控件的类层次非常简单,而且与DX紧密耦合,Amazon上也有人说代码有版本问题。

就这样吧。

2006年03月15日

ms 比较麻烦,看了大部分 ,又ms没什么

2006.3.19
ps 后面一些math有点烦人呀

2006年03月14日

Shih-Fu Chang, 2001 Overview of the MPEG-7 Standard.pdf

Sylvie Jeannin and Ajay Divakaran 2001 MPEG-7 Visual Motion Descriptors

Miroslaw Bober MPEG-7 Visual Shape Descriptors 2001