抠腚爱揉曼 Coding Iron Man

28Dec/113

虚跟模板可以兼得?!

Posted by Jay

由某个案例引发出来...

为什么要虚

在OOP的开发过程中,往往会有大量对象的定义和使用,很多时候我们需要将这些对象持久化的保持在外部文件里面,然后再在需要的时候将其读取进来。因为对象的类型数量都很多,一个一个去实现如何写入和读取就特别冗余且笨重。常见的解决方式便是序(反序)列化,简而言之就是为对象的基类都加上serialize/deserialize的虚方法,然后子类只需要实现如何组织需要写入和读取的数据就可以了。此后我们只要有一个serializable的对象便能对其进行读写了。

为什么要模板

但是关于具体的序列化的时候,便会遇到一些跟平台相关的问题,比如不能确定这个对象最后写入到的是一个本地文件、网络流还是JSON这样的中间载体的时候该怎么办?因此我们提供给序列化接口的序列化器这时变得不那么确定了。此外我们也不希望对象的序列化接口明确的知道序列化器是什么,因为这样会让其产生依赖使得对象的使用不独立。综上,我们希望一种模板化的序列化器,只有到对象用到它的时候才会被具体的展开使用。

虚跟模板可以兼得?!

所以我们需要给对象的基类定义一个方法看上去会是:

template<typename Serializer>
virual void Serialize(Serializer& serializer);

但是遗憾的告诉你这是不可能的。为什么?
首先抛开C++的标准不说,单从编译器的实现上面就能否定这一点。虚函数具体是通过虚函数表来表现其多态性的,这个虚函数表实际上是一个函数地址的数组,在建立这个虚函数表的时候所有的虚函数都需要被实现了,这样才能找到有效的函数地址。但另一方面模板方法本身是并不存在的方法,只有在被用到的时候才被具体展开并实例化。因此模板方法的“不确定”性与虚函数的“必须确定”性在理论上便已经矛盾冲突了。

解决方案

首先,为了模板化,不得不先将virtual去掉,这样要求所有对象里面的序列号方法被重新声明为:

template<typename Serializer>
void Serialize(Serializer& serializer);

但是作为序列化方法的调用者,它所能拿到的是所有对象的基类的指针或者引用,它也不能直接调用到子类里面的具体方法。为此目前我能想出的办法就是向下转型,例如:

void Serialize(Base& base)
{
    FooSeraizlier fooSerializer;
    switch(base.GetClassId())
    {
    case DeviedA::ClassID:
        (static_cast<DeviedA*>(&base))->Serialize(fooSerializer);
        break;
    case DeviedB::ClassID:
        (static_cast<DeviedB*>(&base))->Serialize(fooSerializer);
        break;
    case DeviedC::ClassID:
        (static_cast<DeviedC*>(&base))->Serialize(fooSerializer);
        break;
    ......
    }
}

无奈的是类似这样switch case的向下转型必须要手写的,因此当类型过多的时候肯定是很悲剧的,但是如果你有类似python一类的脚本工具来帮你自动生成代码的话,那就放心的使用吧。

Tagged as: 3 Comments