Loki库读解随感二:类型间耦合检测和去耦合
过了如许之久才有这随感二,实在不好意思。原因是我虽然读懂了Loki的每一行代码,却实在未能理解如何去使用这些代码,直到近来才渐渐有所悟的。
数据类型之间的联系主要有两类:一,类型之间存在着自动转换关系;二,类型间存在着继承关系,虽然它其实也表明了某种转换(主要是对象切片和指针向上映射)。
那么,如何判断类型间存在转换或继承呢?Loki库TypeManip提供了很精彩很完美的解决方法(注:《More Exceptional C++》Item 4也给出了讲解,可以参考。):
//WQ注:和以前讨论过的TypeList一样,Conversion类也是供编译期使用
//的,所以靠提供萃取来解决问题的。
template <class T, class U>
struct Conversion
{
typedef Private::ConversionHelper<T, U> H;
#ifndef __MWERKS__
//WQ注:这是精妙所在!讲解太长,放到后面进行。
enum { exists = sizeof(typename H::Small) == sizeof(H::Test(H::MakeT())) };
#else
enum { exists = false };
#endif
enum { exists2Way = exists && Conversion<U, T>::exists };
enum { sameType = false };
};
//WQ注:用偏特化解决相同类型
template <class T>
struct Conversion<T, T>
{
enum { exists = 1, exists2Way = 1,sameType = 1 };
};
// WQ注:用偏特化和特化解决void
template <class T>
struct Conversion<void, T>
{
enum { exists = 1, exists2Way = 0,sameType = 0 };
};
template <class T>
struct Conversion<T, void>
{
enum { exists = 1, exists2Way = 0,sameType = 0 };
};
template <>
class Conversion<void, void>
{
public:
enum { exists = 1, exists2Way = 1,sameType = 1 };
};
}
先看辅助类ConversionHelper的定义:
template <class T, class U>
struct ConversionHelper
{
typedef char Small;
struct Big { char dummy[2]; };
//WQ注:注意,下面三个函数并没有实现体!
static Big Test(...);//WQ注:C++中,不定参数已不需要“至少一个定参”了。
static Small Test(U);
static T MakeT();
};
看其中的Test函数,如果T到U之间存在转换关系的话,根据重载决策,肯定调Big Test(U)函数。依靠两个函数的返回类型并不相同,就可以判断出调了那个版本,于是也就推测出T到U之间是否存在转换关系了。
回过头来再看前面的使用:sizeof(typename H::Small) == sizeof(H::Test(H::MakeT()));由于我们讲过,Conversion是编译期使用的类,不想采用对返回值采用运行期比较运算,恰好有sizeof()运算满足要求。所以,ConversionHelper将两个Test函数的返回类型实现得在大小上不等。
令人崩溃的事来了:由于我们只使用了Big和Small的大小而没有用它的值,所以,可以根本不用真的创建返回对象,Test函数也不必真的被调用,于是所有行为都是编译期的了。C++说,你可以不用创建并没有被真的使用的东西,所以,Test函数可以不用被定义,只要有这个申明就行了。是的,Loki库中确实没有定义这三个函数!崩溃啊,崩溃!在没搞懂这一点之前,我反复搜索了全部源码,并认为我下载了一个不完全的版本,然后四处搜索最新版本,着实乱了好一阵。虽然我不敢说所有C++编译器都能支持这一点,但我使用的VC、BCB、DevCPP三个主流编译器都正确支持了这一点,Loki的源码能够正确编译和使用。
如何判断两个类型间存在继承关系?很简单:派生类指针类型可以自动向上映射为基类指针类型。当然还得排除“同类型指针间”和“非void *与void *间”这两种情况。Loki库源码如下:
// macro SUPERSUBCLASS
// Invocation: SUPERSUBCLASS(B, D) where B and D are types.
// Returns true if B is a public base of D, or if B and D are aliases of the
// same type.
//
// Caveat: might not work if T and U are in a private inheritance hierarchy.
#define SUPERSUBCLASS(T, U) /
(::Loki::Conversion<const U*, const T*>::exists && /
!::Loki::Conversion<const T*, const void*>::sameType)
// macro SUPERSUBCLASS_STRICT
// Invocation: SUPERSUBCLASS_STRICT(B, D) where B and D are types.
// Returns true if B is a public base of D.
//
// Caveat: might not work if T and U are in a private inheritance hierarchy.
#define SUPERSUBCLASS_STRICT(T, U) /
(SUPERSUBCLASS(T, U) && /
!::Loki::Conversion<const T, const U>::sameType)//WQ注:有人指出此处最好使用const T *,const U *,但确实T和U就足够了,只是编译耗时可能要多一点点,反正运行期都是没有任何开销。
故事还没有结束,提供的萃取值是int的0或1(当然也可以认为等价于bool的false或true),它们只能作运行期运算的,那么如何编译期使用这些结果?Loki库同样有精彩的解决方法:
// class template Int2Type
// Converts each integral constant into a unique type
// Invocation: Int2Type<v> where v is a compile-time constant integral
// Defines 'value', an enum that evaluates to v
template <int v>
struct Int2Type
{
enum { value = v };
};
// class template Select
// Selects one of two types based upon a boolean constant
// Invocation: Select<flag, T, U>::Result
// where:
// flag is a compile-time boolean constant
// T and U are types
// Result evaluates to T if flag is true, and to U otherwise.
template <bool flag, typename T, typename U>
struct Select
{
typedef T Result;
};
template <typename T, typename U>
struct Select<false, T, U>
{
typedef U Result;
};
Select,一个编译期的?:运算。编译期只能靠类型推导(模板),类型判决(重载)来选择不同的分支。Int的0和1自然不在这两种情况之列,但Int2Type<0>和Int2Type<1>当然是满足的。依靠偏特化,Select圆满完成了它的任务。
类型之间有自动转换当然是极有用的,但很多时候却也造成麻烦:一,很容易产生转换多径,尤以多继承时最为麻烦;二,发生期望外的转换而得到期望外的行为,尤以重载时的情况最为混乱;三,转换不够精确,不能完全满足需要,问题发生在向下类型映射时。
在适当的时候去除了类型间耦合就可以避免这三方面副作用。
类型间去耦合有两种方法:一,用模板封装;二,指向指针的指针。
方法一的实现太简单了:
template<typename T>
struct Type2Type
{
typedef T OriginalType;
}
多个T之间的转换和继承关系在Type2Type<T>之间再不存在了。更为神奇的是,向下类型映射将可以精确进行的,但这得结合其具体使用来讲,我把它留到下一篇《多继承的改良》中讲。