抠腚爱揉曼 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
16Aug/103

SFINAE

Posted by Jay

什么是SFINAE

在C++中有很多的编程技巧(Trick),SFINAE就是其中一种,他的全义可以翻译为“匹配失败并不是一个错误(Substitution failure is not an error)”。简单来说他就是专门利用编译器匹配失败的一种技巧。

案例

比如我们想实现一个通用的函数叫AnyToString,他可以实现任意类型的数据转成字符串:

template<typename ValueType>
char* AnyToString(const ValueType& value);

我们更希望这个函数能检查ValueType类型自己有没有ToString方法,如果有就直接调用,没有的话就采取通用的处理方案。但是C++没有反射机制,不能像C#那样通过TypeInfo来检查,更没有像Java那样纯粹的OOP,从最基类就定义了ToString方法,下面的子类只用负责重载。
所以我们希望能有一种方法能让C++也能检查某个类型是否定义了某个成员函数,这就可以用到SFINAE。

5Aug/102

N!

Posted by Jay

template<int N>
struct Factorial
{
    static const int Result = N * Factorial<N - 1>::Result;
};

template<>
struct Factorial<0>
{
    static const int Result = 1;
};
Tagged as: , 2 Comments
21Jul/100

C++中枚举与字符串相互转换

Posted by Jay

前言

有的时候我们喜欢使用一些外部的文件保存管理一些配置信息,这些配置文件大多都是文本格式例如ini,xml等,这样方便编辑和管理。因此在使用的过程中必然会遇到各种字符串转换问题。
最常见的便是将字符串的数字转换为对应的整形(integer)或者浮点(float),如果遇到枚举类型,可能便会想当做是整形来处理,但觉得不是特别理想。如果能有办法直接转换为枚举会方便很多。

2Jul/100

关于C++模板函数的一种简化代理

Posted by Jay

什么是模板

C++相较于C的一大特点除了OOP,便是它的模板化(template)功能。这也是别的语言(除了C#以外)所没有的优势。

什么是模板?简而言之它是一种预处理功能(pre-processing function),类似宏(Macro),不过比后者有着无比强大的优势。当然在C++编程里面,宏的地位也是不可替代的,而且宏经常可以和模板一起使用达到神乎其神的效果(例如boost)。
模板有具体分为模板函数(template function)和模板类(template class),这里主要是要讨论一下关于模板函数的一些问题。

27Jun/103

关于C++中的类型转换函数

Posted by Jay

函数原型

C++里面每个类型(class or struct)都可以定义自己的成员函数(方法)以及成员变量(属性),这些都是很常见基本的东西,类似的java,C#或者任何一种OOP都应该有的东西。
关于函数其中有一点比较重要的便是其函数原型,例如:

Prototype: ReturnType FunctionName(ArgumentList)
Example: int sum(int valueA, int valueB)