《Modern C++ Design》源码Loki库读解随感二:类型间耦合检测和去耦合

Loki库读解随感二:类型间耦合检测和去耦合

过了如许之久才有这随感二,实在不好意思。原因是我虽然读懂了Loki的每一行代码,却实在未能理解如何去使用这些代码,直到近来才渐渐有所悟的。

 

      数据类型之间的联系主要有两类:一,类型之间存在着自动转换关系;二,类型间存在着继承关系,虽然它其实也表明了某种转换(主要是对象切片和指针向上映射)。

      那么,如何判断类型间存在转换或继承呢?LokiTypeManip提供了很精彩很完美的解决方法(注:《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函数,如果TU之间存在转换关系的话,根据重载决策,肯定调Big Test(U)函数。依靠两个函数的返回类型并不相同,就可以判断出调了那个版本,于是也就推测出TU之间是否存在转换关系了。

回过头来再看前面的使用:sizeof(typename H::Small) == sizeof(H::Test(H::MakeT()));由于我们讲过,Conversion是编译期使用的类,不想采用对返回值采用运行期比较运算,恰好有sizeof()运算满足要求。所以,ConversionHelper将两个Test函数的返回类型实现得在大小上不等。

令人崩溃的事来了:由于我们只使用了BigSmall的大小而没有用它的值,所以,可以根本不用真的创建返回对象,Test函数也不必真的被调用,于是所有行为都是编译期的了。C++说,你可以不用创建并没有被真的使用的东西,所以,Test函数可以不用被定义,只要有这个申明就行了。是的,Loki库中确实没有定义这三个函数!崩溃啊,崩溃!在没搞懂这一点之前,我反复搜索了全部源码,并认为我下载了一个不完全的版本,然后四处搜索最新版本,着实乱了好一阵。虽然我不敢说所有C++编译器都能支持这一点,但我使用的VCBCBDevCPP三个主流编译器都正确支持了这一点,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 *,但确实TU就足够了,只是编译耗时可能要多一点点,反正运行期都是没有任何开销。

      故事还没有结束,提供的萃取值是int01(当然也可以认为等价于boolfalsetrue),它们只能作运行期运算的,那么如何编译期使用这些结果?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,一个编译期的?:运算。编译期只能靠类型推导(模板),类型判决(重载)来选择不同的分支。Int01自然不在这两种情况之列,但Int2Type<0>Int2Type<1>当然是满足的。依靠偏特化,Select圆满完成了它的任务。

 

      类型之间有自动转换当然是极有用的,但很多时候却也造成麻烦:一,很容易产生转换多径,尤以多继承时最为麻烦;二,发生期望外的转换而得到期望外的行为,尤以重载时的情况最为混乱;三,转换不够精确,不能完全满足需要,问题发生在向下类型映射时。

      在适当的时候去除了类型间耦合就可以避免这三方面副作用。

      类型间去耦合有两种方法:一,用模板封装;二,指向指针的指针。

方法一的实现太简单了:

template<typename T>

struct Type2Type

{

    typedef  T  OriginalType;

}    

      多个T之间的转换和继承关系在Type2Type<T>之间再不存在了。更为神奇的是,向下类型映射将可以精确进行的,但这得结合其具体使用来讲,我把它留到下一篇《多继承的改良》中讲。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值