《Modern C++ Design》源碼Loki庫讀解隨感二:類型間耦合檢測和去耦合


Loki庫讀解隨感二:類型間耦合檢測和去耦合<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

過了如許之久才有這隨感二,實在不好意思。原因是我雖然讀懂了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>之間再不存在了。更為神奇的是,向下類型映射將可以精確進行的,但這得結合其具體使用來講,我把它留到下一篇《多繼承的改良》中講。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com