2008年7月27日 星期日

用 traits classes 以表現型別的資訊

Traits classes 是一種在 STL 中廣泛應用的型別辨視技術,考慮以下情境,我們有四種自定型別 ClassA, ClassB, ClassC, ClassD,以及一個 function template F 如下:
template<typename ClassT>
void F( ClassT myType)
{
if ( myType is ClassA)
{
// operating for ClassA
}
else
{
// operaing for ClassB, C, D
}
}


雖然 F 可以對這四種型別做運算,但有時候可能因為此四種型別的實作方式不一,某些型別對於 F 運算,可以有更有效率的作法 (或是不同的作法),例如 STL 中,對於某種 iterator 的運算,會因為 iterator 的不同類型 (STL 有五種 iterators:input, output, forward, bidirectional, random access) 而有不一樣的方法,所以我們希望能在 template 中辨視型別。

首先我們建立一些 tag struct 用以表示型別的特徵:
struct classA_tag {} ;

struct classBCD_tag {} ;


然後在目的型別中,利用 typedef 定義一個子型別
class ClassA
{
public:
typedef classA_tag category ;
};


class ClassB
{
public:
typedef classBCD_tag category ;
};


class ClassC
{
public:
typedef classBCD_tag category ;
};


class ClassD
{
public:
typedef classBCD_tag category ;
};


接著,建立 traits class template:
template<typename ClassT>
struct MyTraits
{
typedef typename ClassT::category category;
};


此時我們的 function template F 大概變成這樣:
template<typename ClassT>
void F( ClassT myType)
{

if ( typeid (typename myTraits<ClassT>::category ) == typeid (classA_tag ) )
{
// operating for ClassA
}
else
{
// operaing for ClassB, C, D
}
}


但是這樣的程式碼在編譯時可能會有問題 (因為 operating for ClassA 當中的所有敘述,必需被 ClassT 型別所支援,既使這些敘述不會被執行到),但此時有件更重要的事要解決,ClassT 也就是 myType 的型別在編譯時期就可以決定,而myTraits<ClassT>::category 也是在編譯時期就可以決定,但 if else 卻是執行期才會執行的語句,因此我們必須將 if else 提到編譯時期確定,做法即是使用 function overloading。我們 overload 兩個 function template F如下:
template<typename ClassT>
void doF( ClassT myType, classA_tag)
{
// operaing for ClassA
}


template<typename ClassT>
void doF( ClassT myType, classBCD_tag)
{
// operaing for ClassB, C, D
}


最後,我們就可以將 F 改寫如下:
template<typename ClassT>
void F( ClassT myType)
{
doF ( myType , typename MyTraits<ClassT>::category( ) );
}


完工。

沒有留言: