虚跟模板可以兼得?!
由某个案例引发出来...
为什么要虚
在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一类的脚本工具来帮你自动生成代码的话,那就放心的使用吧。
SFINAE
什么是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。
N!
template<int N>
struct Factorial
{
static const int Result = N * Factorial<N - 1>::Result;
};
template<>
struct Factorial<0>
{
static const int Result = 1;
};
C++中枚举与字符串相互转换
前言
有的时候我们喜欢使用一些外部的文件保存管理一些配置信息,这些配置文件大多都是文本格式例如ini,xml等,这样方便编辑和管理。因此在使用的过程中必然会遇到各种字符串转换问题。
最常见的便是将字符串的数字转换为对应的整形(integer)或者浮点(float),如果遇到枚举类型,可能便会想当做是整形来处理,但觉得不是特别理想。如果能有办法直接转换为枚举会方便很多。
关于C++模板函数的一种简化代理
什么是模板
C++相较于C的一大特点除了OOP,便是它的模板化(template)功能。这也是别的语言(除了C#以外)所没有的优势。
什么是模板?简而言之它是一种预处理功能(pre-processing function),类似宏(Macro),不过比后者有着无比强大的优势。当然在C++编程里面,宏的地位也是不可替代的,而且宏经常可以和模板一起使用达到神乎其神的效果(例如boost)。
模板有具体分为模板函数(template function)和模板类(template class),这里主要是要讨论一下关于模板函数的一些问题。
关于C++中的类型转换函数
函数原型
C++里面每个类型(class or struct)都可以定义自己的成员函数(方法)以及成员变量(属性),这些都是很常见基本的东西,类似的java,C#或者任何一种OOP都应该有的东西。
关于函数其中有一点比较重要的便是其函数原型,例如:
Prototype: ReturnType FunctionName(ArgumentList) Example: int sum(int valueA, int valueB)