C++语言中的元类编程(七)

来源:互联网 发布:传智播客java讲师 编辑:程序博客网 时间:2024/06/04 09:28
做过网络或者多媒体应用开发的朋友应该会经常的接触一些协议(或文件格式,以下统称协议),特别是那些二进制协议,这些协议不但晦涩难懂,而且编写处理协议的代码也很麻烦,我们经常需要对着文档,一个bit一个bit的去分析我们的处理逻辑,而如果我们对协议的理解有偏差,产生了一些bug,那调试和修改这些bug也是极为费力的(特别是调试别人的或旧的代码,这种问题更加突出)。

此外,通常处理一种协议的代码也是专用的,当我们需要增加一种新协议的支持时,我们不得不重写一套新的代码,然后反复的测试,调试,解bug。这样的开发效率是很低的,而且成本也很高。

如果用传统的编程方法,我们很难避免这些问题,然而如果采用元类编程思想,这些问题就会变得相对简单得多。在正式开始介绍处理方法之前,先让我们来假设一个简单的协议,因为实际运用的协议大多都很复杂,而且很多朋友可能没有相关经验,缺乏背景知识,这可能会让他们在理解后面的设计时感到困难。

我们每个人小的时候应该都玩过跳棋,就是那种由6种颜色的玻璃小弹珠和六角星形棋盘组成的游戏棋,我们不妨就借用这6种不同颜色的小弹珠来讨论我们的协议。首先,我们可以规定,这六种小弹珠的特定组合具有特定的含义,比如“红黄蓝”代表“你”,而“红绿蓝”代表“我”,那么我们就可以利用这些小弹珠排列成一个序列,来表达出一种特定的含义。很显然,我们需要规定什么样的组合代表什么样的含义,这就是一种协议。(如果你是一个显赫的人物或是一个社会名流,当你想给情人写信的时候,你就可以这样写:红黄白绿黄蓝黑黑绿黄蓝白绿……,而你的情人就可以利用这个协议把你要说的意思翻译出来,万一这封信遗失了也不用担心,只要其他人不知道你的协议,他/她就不会知道你的秘密,除非他是个密码学家)如果我们要让计算机来处理,就需要对这6种颜色的小弹珠进行编码,这样我们就能将一个小弹珠的序列转换成bit流了(自然,反向也成立)。而我们不妨将这种协议中的特定组合称为field,而这种协议的基本组成单元——6种颜色的小弹珠,自然也是6种最基本的field。好,现在来看看我们如何运用元类编程思想来处理这种协议,并能带来哪些优势。

让我们先来看看从bit流得到field序列的情况,第一步,我们需要设计一个元类来描述每一种field。为避免我们纠结于具体协议的细节,下面我直接给出一个定义:

class CBitStream; // 这是一个已实现的处理bit流的类,我们不用关心这个类的具体实现,只需要知道这个类提供了访问一段buffer中的某个(或某些)bit的方法

typedef CBitStream::CIterator stream_iterator; // CBitStream采用了iterator的设计,同样,我们不需要知道这个iterator的实现细节,只需要知道它是访问某个(或某些)bit的接口

struct IProtocolDescriptor; // 如之前所说,有些field是有结构的,这种field我们可以把它看作是一种子协议

struct IFieldDescriptor {

    virtual unsigned ID() const = 0; // 为提高调用函数的处理效率,我们需要一个ID来唯一标识这种field

    virtual const char * Name() const = 0; // 这个容易理解

    virtual bool IsCondition() const = 0; // 指示这种field是否是另一种field的条件,参见ConditionFieldDescriptor

    virtual const IFieldDescriptor * ConditionFieldDescriptor() const = 0; // 用来标识这种field是否由另一种field来控制,参见IsCondition

    virtual const IProtocolDescriptor * CastToProtocolDescriptor() const = 0; // 参见IProtocolDescriptor的注释,如果这个转换能成功,说明这是一种子协议field

    virtual size_t FieldSize(stream_iterator fieldData) const = 0; // 这种field的大小(单位是bit),如果是变长编码的field,我们可以根据它的内容计算出来

    virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const = 0; // 这种field在bit流中的个数,如果不存在,应当返回0,否则我们可以根据它的条件field的内容和其本身内容计算出来

};

注意上面的定义中IsCondition和ConditionFieldDescriptor用于描述两种field的依赖关系,而FieldCount则是(运行时)取得field对象个数的元函数。另外,它提到了IProtocolDescriptor接口(以及与这个接口的关系),其定义如下:

struct IProtocolDescriptor {
    virtual size_t GetFieldDescriptorCount() const = 0;
    virtual const IFieldDescriptor * GetFirstFieldDescriptor() const = 0;
    virtual const IFieldDescriptor * GetNextFieldDescriptor()  const = 0;
}; // 注意这个接口描述的是一个协议field由多少个子field组成,以及如何访问这些子field的描述,它与field的对象在bit流中的分布不是一回事。

结合这两个定义,不难发现,我们可以用它们描述出一个树状逻辑结构,比如:

struct image {

    struct header_t {

        short width;

        short height;

    } image_header;

    unsigned char image_data[];

};

上面是一个简化版的位图格式的数据结构,用我们的IFieldDescriptor和IProtocolDescriptor描述出来会像这样:

enum image_des_id {

    image_des = 1,

    image_des_width,

    image_des_height,

    image_des_header,

    image_des_data

};

class CImageHeaderWidthDes: public IFieldDescriptor {

    virtual unsigned ID() const { return image_des_width; }

    virtual const char * Name() const { return "image_des_width"; }

    virtual bool IsCondition() const { return false; }

    virtual const IFieldDescriptor * ConditionFieldDescriptor() const { return NULL; }

    virtual const IProtocolDescriptor * CastToProtocolDescriptor() const { return NULL; }

    virtual size_t FieldSize(stream_iterator fieldData) const { return 16; }

    virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const { return 1; }

};

class CImageHeaderHeightDes: public IFieldDescriptor {

    ...

}; // 请仿造CImageHeaderWidthDes将这个定义补充完整

class CImageHeaderDes : public IFieldDescriptor, public IProtocolDescriptor {

    virtual unsigned ID() const { return image_des_header; }

    virtual const char * Name() const { return "image_des_header"; }

    virtual bool IsCondition() const { return true; }

    virtual const IFieldDescriptor * ConditionFieldDescriptor() const { return NULL; }

    virtual const IProtocolDescriptor * CastToProtocolDescriptor() const { return this; }

    virtual size_t FieldSize(stream_iterator fieldData) const { return 0; } // 我们可以不关心子协议类型的field的大小,至于具体原因,我们将来会再解释

    virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const { return 1; }

    virtual size_t GetFieldDescriptorCount() const { return 2; }

    virtual const IFieldDescriptor * GetFirstFieldDescriptor() const {

        const_cast<int &>(mSubFieldDesIdx) = 0;

        return mSubFieldDes[0];

    }

    virtual const IFieldDescriptor * GetNextFieldDescriptor()  const {

        ++ const_cast<int &>(mSubFieldDesIdx);

        if (mSubFieldDesIdx < 2)

            return mSubFieldDes[mSubFieldDesIdx];

        return NULL;

    }

    CImageHeaderWidthDes mWidthDes;

    CImageHeaderHeightDes mHeigthDes;

    const IFieldDescriptor * mSubFieldDes[2];

    int mSubFieldDesIdx;

public:

    CImageHeaderDes() {

        mSubFieldDes[0] = &mWidthDes;

        mSubFieldDes[1] = &mHeigthDes;

        mSubFieldDesIdx = 0;

    }

};

class CImageDataDes: public IFieldDescriptor {

    virtual unsigned ID() const { return image_des_data; }

    virtual const char * Name() const { return "image_des_data"; }

    virtual bool IsCondition() const { return false; }

    virtual const IFieldDescriptor * ConditionFieldDescriptor() const { return mImageHeaderDes; }

    virtual const IProtocolDescriptor * CastToProtocolDescriptor() const { return NULL; }

    virtual size_t FieldSize(stream_iterator fieldData) const { return 8; }

    virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const {

        short size[2] = {0};

        conditionFieldData.CopyBits( 32, &size, sizeof(size) ); // 这个操作从conditionFieldData中拷贝32bits到size中。

        return (int) size[0] * (int) size[1] * 4;

    }

    const IFieldDescriptor * mImageHeaderDes;

public:

    CImageDataDes(const IFieldDescriptor * imageHeaderDes) {

        mImageHeaderDes = imageHeaderDes;

    }

};

class CImageDes: public IFieldDescriptor, public IProtocolDescriptor {

    ... // 省略接口IFieldDescriptor和IProtocolDescriptor的实现,请仿造CImageHeaderDes自行补充完整

    CImageHeaderDes mImageHeaderDes;

    CImageDataDes mImageDataDes;

    const IFieldDescriptor * mSubFieldDes[2];

    int mSubFieldDesIdx;

public:

    CImageDes(): mImageDataDes(&mImageHeaderDes) {

        mSubFieldDes[0] = &mImageHeaderDes;

        mSubFieldDes[1] = &mImageDataDes;

        mSubFieldDesIdx = 0;

    }

};

从上面的例子中,我们可以清晰的看到接口IFieldDescriptor和IProtocolDescriptor的描述能力,同时这种表达方式也非常的便于我们(根据原始的结构)检查每一个描述是否是正确的,同时我们也应当看到,这种描述能力是非常灵活的,可以任意的根据我们的需要进行扩展(比如我们最初提出这个设计是为了描述那个“弹珠”协议,但是居然也可以描述位图!而且只要是能用树形表示的绝大多数逻辑结构,它都能描述!),这样我们会惊奇的发现,此时一个协议具体是怎么规定的已经不重要了,重要的是如何利用IFieldDescriptor和IProtocolDescriptor描述出的数据结构,去分析bit流,并且输出一个field的序列。


0 0