C++泛型与多态(3):类模板特化

C++泛型与多态(3):类模板特化

转载自:https://www.jianshu.com/p/9675085cb49e

作者:袁英杰

C++的类没有重载,所以类只能依靠特化来实现多态。

例子:斐波那契数列

斐波那契数列(Fibonacci Number)是一个经典的数学问题。解决这类问题,是FP的强项。下面是一个Haskell的实现。

fib :: Integer -> Integerfib 0 = 0fib 1 = 1fib n = fib (n-1) + fib (n-2)

从其实现可以看出,函数式编程解决这类问题的两种武器:

模式匹配递归

而类模板特化,也是同样的思路:

template struct Fib{ static const U64 value = Fib::value + Fib::value; }; template struct Fib { static const U64 value = 1; }; template struct Fib { static const U64 value = 0; }; 例子:数列求和

我们再看另外一个例子:对任意一个整数数列求和。在不使用fold的情况下,Haskell的实现方式如下:

sum :: [Int] -> Intsum [] = 0sum (x:xs) = x + (add xs)

其中,整数数列的长度不确定。如果用C++的模版来实现,则需要使用C++11的变参模版:

template struct SUM;template struct SUM { static const int value = T_HEAD + SUM::value;};template struct SUM { static const int value = 0;};

从这两个例子可以看出,C++模版解决问题的思路和FP是完全一样。正是因为模板具备了这样的能力,所以它是图灵完备的。

因而,我们也可以清晰的看出,模版的特化像模式匹配一样,是一种ad-hoc多态。

给出这两个例子,仅仅是为了展示模版特化的本质。在实际应用中,肯定不是为了例子中的计算数值。而是通过编译时和运行时的结合,为程序员提供强大的武器。

动静结合:mockcpp

mockcpp支持对于自由函数的mock。其中一种实现技术称之为 Api Hook: 为了能够mock一个自由函数,就需要对自由函数的调用进行拦截,并将控制权转交给mock框架。

而拦截技术的关键,就是将被mock自由函数的地址替换为mockcpp为之生成的hook。而每个被mock的自由函数都有一个对应的hook。

这种一一映射关系,就可以通过模板特化的技术来解决。我们先定义一个特化模板ApiHookFunctor。其中,我们仅仅列出了针对两参数函数的特化模板。针对带有其它参数个数函数的特化模板的算法完全一样,这里就不再列出。

// 基础模板template struct ApiHookFunctor{};// 2 个参数函数的特化template struct ApiHookFunctor{private: typedef R FUNC (T1, T2); // 2 个参数的 hook static R hook(T1 p1, T2 p2) { // 控制权转交给 mockcpp 框架 return GlobalMockObject::instance.invoke(apiAddress)(p1, p2); } public: static void* getApiHook(FUNC* api) { if(not appliedBy(api)) return 0; ++refCount; return getHook(); } static void* applyApiHook(FUNC* api) { if(apiAddress != 0) return 0; apiAddress = reinterpret_cast(api); refCount = 1; return getHook(); } static bool freeApiHook(void* hook) { if(getHook() != hook) return false; freeHook(); return true; } private: static bool appliedBy(FUNC* api) { return apiAddress == reinterpret_cast(api); } static void* getHook() { return reinterpret_cast(hook); } static void freeHook() { if(--refCount == 0) apiAddress = 0; } private: static void* apiAddress; // 原函数地址 static unsigned int refCount; // 引用计数 };// ...

如果两个函数如果有相同的函数原型,比如:

// 这两个函数具有相同的类型int f(int, double);int g(int, double);

虽然f和 g是两个不同的函数,但它们的类型是相同的。但之前我们已经介绍过,Api Hook设计的关键在于,要为每一个需要mock 的自由函数都应该生成一个hook。所以,上述模板必须能够为同一类型的函数预先分配多个hook,以供多个同一类型的不同函数进行动态分配。

而模板参数SEQ正是为了区分同一类型函数的多个hook。每一个经过实例化之后的模板类都是一个可动态分配的资源,因为一个这样的模板类对应了一个hook。

而每个模板的静态数据apiAddress用来纪录当前模板类被分配给了哪个函数, 其内容为函数的原始地址。当一个自由函数被多次mock 时,refCount用来纪录mock的次数。当其值为0时,此模板类将会被释放,以供后续的分配。

ApiHookFunctor用来生成单个资源,而下面的模板:ApiHookGenerator则用来生成和管理所有资源。这是一个动态和静态相结合,进行资源分配和释放的典型处理手法。其中,编译时(静态)生成所有可使用的资源,以及相关的算法代码;而运行时(动态)则根据静态生成的算法,动态的管理静态生成的资源。如下:

template struct ApiHookGenerator { static void* findApiHook(F* api) { void* hook; (hook = ApiHookFunctor::getApiHook(api)) || (hook = ApiHookGenerator::findApiHook(api)); return hook; } static void* applyApiHook(F* api) { void* hook; (hook = ApiHookFunctor::applyApiHook(api)) || (hook = ApiHookGenerator::applyApiHook(api)); return hook; } static bool freeApiHook(void* hook) { return (ApiHookFunctor::freeApiHook(hook)) || (ApiHookGenerator::freeApiHook(hook)); }}; template struct ApiHookGenerator { static void* findApiHook(F* api) { return 0; } static void* applyApiHook(F* api) { return 0; } static bool freeApiHook(void* hook) { return true; } };

有了ApiHookGenerator这个资源管理器之后,用户就可以通过如下方式来使用:

template struct ParameterizedApiHookHolder : public ApiHookHolder{ const static unsigned int MAX_RESOURCES = 10; ParameterizedApiHookHolder(F* api) { (m_hook = ApiHookGenerator::findApiHook(api)) || (m_hook = ApiHookGenerator::appyApiHook(api)); } void * getApiHook() const { return m_hook; } ~ParameterizedApiHookHolder() { ApiHookGenerator::freeApiHook(m_hook); }private: void* m_hook; };

最后,为了有助于进一步理解上述代码,我们来总结一下解决问题的整个思路:

每个被mock函数,都要有一个自己的hook函数;每个hook函数,必须和原函数的原型完全一样;由于函数原型存在无穷个种类,所以我们必须借助模板,按需在编译时生成hook资源;不同函数可能属于同一类型,所以需要给同一类型的的函数预留多份 hook资源;有了多份资源,就需要按照一定的算法进行管理。于是为资源模板引入SEQ参数;资源的生成和管理算法,都是以SEQ来区分同一类型函数的多份hook 资源。由于每个函数的运行时地址都是唯一的,所以,使用“函数地址”作为key值,在运行时,按照静态生成的算法,动态地分配编译时按需生成的hook资源。 动静结合:组合

Transaction DSL中可以这样描述一个序列:

__sequential ( __asyn(Action1) , __asyn(Action2) , __sync(Action3))

__sequantial里包含的Action完全由用户根据自己的需要填写,因而数量不确定。

而__sequential这个宏背后直接对应的就是变参模版:

#define __sequential(...) SEQUENTIAL__< __VA_ARGS__ >

而模版SEQUENTIAL__的实现如下:

template struct GenericSequential;template struct GenericSequential : GenericSequential{protected: void init() { GenericSequential::pushBackAction(action); GenericSequential::init(); } private: details::LINKED__ action;};template struct GenericSequential : SchedSequentialAction{protected: void init() {}};template struct SEQUENTIAL__ : GenericSequential{ SEQUENTIAL__() { GenericSequential::init(); }};

这里例子展示的是,对于不确定数量的被组合元素,通过变参模版在编译时完成对于类型的静态组合。然后运行时,让模版类里的函数完成动态组合。

小结


比丘资源网 » C++泛型与多态(3):类模板特化

发表回复

提供最优质的资源集合

立即查看 了解详情