traits技術學習總結

一、基礎篇

Traits技術可以用來獲得一個 類型 的相關信息的。 首先假如有以下一個泛型的迭代器類,其中類型參數 T 爲迭代器所指向的類型:

template
 <typename T>
class myIterator
{
 ...
};

當我們使用myIterator時,怎樣才能獲知它所指向的元素的類型呢?我們可以爲這個類加入一個內嵌類型,像這樣:
template <typename T>
class myIterator
{
      typedef  Tvalue_type; 
...
};
這樣當我們使用myIterator類型時,可以通過 myIterator::value_type來獲得相應的myIterator所指向的類型。

現在我們來設計一個算法,使用這個信息。
template <typename T>
typename
 myIterator<T>::value_typeFoo(myIterator<T> i)
{
 ...
}
這裏我們定義了一個函數Foo,它的返回爲爲  參數i 所指向的類型,也就是T,那麼我們爲什麼還要興師動衆的使用那個value_type呢? 那是因爲,當我們希望修改Foo函數,使它能夠適應所有類型的迭代器時,我們可以這樣寫:
template <typename I> //這裏的I可以是任意類型的迭代器
typename I::value_type Foo(I i)
{
 ...
}
現在,任意定義了 value_type內嵌類型的迭代器都可以做爲Foo的參數了,並且Foo的返回值的類型將與相應迭代器所指的元素的類型一致。至此一切問題似乎都已解決,我們並沒有使用任何特殊的技術。然而當考慮到以下情況時,新的問題便顯現出來了:

原生指針也完全可以做爲迭代器來使用,然而我們顯然沒有辦法爲原生指針添加一個value_type的內嵌類型,如此一來我們的Foo()函數就不能適用原生指針了,這不能不說是一大缺憾。那麼有什麼辦法可以解決這個問題呢? 此時便是我們的主角:類型信息榨取機 Traits 登場的時候了


我們可以不直接使用myIterator的value_type,而是通過另一個類來把這個信息提取出來:
template <typename T>
class Traits
{
      typedef typename T::value_type value_type;
};
這樣,我們可以通過 Traits<myIterator>::value_type 來獲得myIterator的value_type,於是我們把Foo函數改寫成:
template <typename I> //這裏的I可以是任意類型的迭代器
typename Traits<I>::value_type Foo(I i)
{
 ...
}
然而,即使這樣,那個原生指針的問題仍然沒有解決,因爲Trait類一樣沒辦法獲得原生指針的相關信息。於是我們祭出C++的又一件利器--偏特化(partialspecialization):
template <typename T>
class Traits<T*> //注意這裏針對原生指針進行了偏特化
{
      typedef typename T value_type;
};
通過上面這個 Traits的偏特化版本,我們陳述了這樣一個事實:一個 T* 類型的指針所指向的元素的類型爲 T。

如此一來,我們的 Foo函數就完全可以適用於原生指針了。比如:
int * p;
....
int i = Foo(p);
Traits會自動推導出 p 所指元素的類型爲 int,從而Foo正確返回。

二、實例代碼

舉例:
上面提到過,traits可被用於針對不同類型提供不同的實現,那麼下面就舉兩個例子來說明如何實現這一點.
Example 1:
假定我們需要爲某個類設計一個可以對所有類型(包括普通的int/long...,提供了clone方法的複雜類型CComplexObject,及由該類派生的類)進行操作的函數clone,下面,先用OO的方法來考慮一下解決方案.看到前面的條件,最先跳進你腦子裏的肯定是Interface,pure virtual function等等.對於我們自己設計的類CComplexObject而言,這不是問題,但是,對於基本數據類型呢?還有那些沒有提供clone方法的複雜類型呢?(這時候你可能會想,要是Java該多easy,所有類都默認從Object派生,而Object已提供了一個默認的clone方法,但是,要使類真正支持clone,還必須implements Cloneable,所以,同樣也不能避免這裏遇到的麻煩).
下面是一個可能的解決方案:
template <typename T, bool isClonable>
class XContainer
{
     ...
     void clone(T* pObj)
     {
         if (isClonable)
         {
             pObj->clone();
         }
         else
         {
             //... non-Clonable algorithm ...
         }
     }
};<span style="color: rgb(153, 0, 0);">
</span>
但是隻要你測試一下,這段代碼不能通過編譯.爲什麼會這樣呢?原因很簡單:對於沒有實現clone方法的非Clonable類或基本類型,pObj->clone這一句是非法的.
那麼怎樣解決上面的這個難題呢?上面不能通過編譯的代碼告訴我們,要使我們的代碼通過編譯,就不能使非Clonable類或基本類型的代碼中出現pObj->clone,即我們需要針對不同類型提供不同的實現.爲了實現這一點,我們可以在我們的模板類中用enum定義一個trait,以標示類是否爲Clonable,然後在原模板類內部引入一個traits提取類Traits,通過對該類進行specilizing,以根據不同的trait提供不同的實現.具體實現如下:
#include <iostream>
using namespace std;

class CComplexObject // a demo class
{
public:
     void clone() { cout << "in clone" << endl; }
};

// Solving the problem of choosing method to call by inner traits class
template <typename T, bool isClonable>
class XContainer
{
public:
     enum {Clonable = isClonable};

     void clone(T* pObj)
     {
         Traits<isClonable>().clone(pObj);
     }

     template <bool flag>
         class Traits
     {
     };

     template <>
         class Traits<true>
     {
     public:
         void clone(T* pObj)
         {
             cout << "before cloning Clonable type" << endl;
             pObj->clone();
             cout << "after cloning Clonable type" << endl;
         }
     };

     template <>
         class Traits<false>
     {
     public:
         void clone(T* pObj)
         {
             cout << "cloning non Clonable type" << endl;
         }
     };
};

void main()
{
     int* p1 = 0;
     CComplexObject* p2 = 0;

     XContainer<int, false> n1;
     XContainer<CComplexObject, true> n2;

     n1.clone(p1);
     n2.clone(p2);
}
編譯運行一下,上面的程序輸出如下的結果:

doing something non Clonable
before doing something Clonable
in clone
after doing something Clonable
這說明,我們成功地根據傳入的isClonable模板參數爲模板實例選擇了不同的操作,在保證接口相同的情況下,爲不同類型提供了不同的實現.

Example
 2:
我們再對上面的例子進行一些限制,假設我們的clone操作只涉及基本類型和CComplexObject及其派生類,那麼我們可以進一步給出下面的解法:

#include <iostream>  
using namespace std;  
  
struct __xtrue_type { }; // define two mark-type  
struct __xfalse_type { };  
  
class CComplexObject // a demo class  
{  
public:  
     virtual void clone() { cout << "in base clone" << endl; }  
};  
  
class CDerivedComplexObject : public CComplexObject // a demo derived class  
{  
public:  
     virtual void clone() { cout << "in derived clone" << endl; }  
};  
  


// A general edtion of Traits  
template <typename T>  
struct Traits  
{  
     typedef __xfalse_type has_clone_method; // trait 1: has clone method or not? All types defaultly has no clone method.  
};  

// Specialized edtion for ComplexObject  
template <>  
struct Traits<CComplexObject>  
{  
     typedef __xtrue_type has_clone_method;  
};  
template <>  
struct Traits<CDerivedComplexObject>  
{  
     typedef __xtrue_type has_clone_method;  
};




template <typename T>  
class XContainer  
{  
     template <typename flag>  //1 
     class Impl{ 
		 
     };  

     template <>  //2 特化
     class Impl <__xtrue_type>{  
		 public:  
			 void clone(T* pObj){  
				 pObj->clone();  
			 }  
     };  

     template <>  //3 特化
     class Impl <__xfalse_type>{  
		 public:  
			 void clone(T* pObj) { 
				 cout<<"can't clone"<<endl;
			 }
     };  
public:  
     void clone(T* pObj)  
     {  
         Impl<Traits<T>::has_clone_method>().clone(pObj);  
     }  
};  
  
void main()  
{  
     int c1=1;;  
	 int *p1 = &c1;

     CComplexObject c2;  
     CComplexObject* p2 = &c2;  

     CDerivedComplexObject c3;  
     CDerivedComplexObject* p3 = &c3; 

     XContainer<int> n1;  
     XContainer<CComplexObject> n2;  
     XContainer<CDerivedComplexObject> n3;  
  
     n1.clone(p1);  
     n2.clone(p2);  
     n3.clone(p3);  
}


三、結語

看到這裏,你或許會說,traits不過如此,還以爲是什麼高深的玩意呢!其實技術就是這樣,說白了都很Easy,關鍵是怎麼將他們用於實際,爲實際的Designing/Development服務.畢竟,在IT領域,不能應用於實際的技術是沒有價值的.

traits是泛型世界中的精靈:小巧,精緻。traits也是泛型編程中最精微的東西,它們往往仰賴於一些編譯期決議的規則,C++標準,和神奇的模板偏特化。這也導致了它們在不同的平臺上可能有不同表現,更常見的是,在某些平臺上根本無法工作。然而,由於它們的依據是C++標準,而編譯器會越來越符合標準,所以這些問題只是暫時的。traits也是構建泛型世界的基本組件之一,它們往往能使設計變得優雅,精緻,甚至完美。

四、升階閱讀

動機

使用traits的動機一般有三種,分派、效率、使某些代碼通過編譯。

 

分派

下面有一個模板函數,假設一個動物收容組織提供了它,他們接受所有無家可歸的可憐的小動物,
於是他們向外界提供了一個函數接受註冊。函數看起來像這樣:

 

template<class T> // T表示接受的是何種動物

void AcceptAnimals(T animal)

{

...  //do something

}; 

但是,如果他們想將貓和狗分開處理(畢竟飼養一隻貓和飼養一隻狗並不相同。
他們可能會爲狗買一根鏈子,而溫順的貓則可能不需要)。一個可行的方法是分別提供兩個函數:
AcceptDog和AcceptCat,然而這種解決辦法並不優雅(想想看,註冊者可能既有一隻貓又有一隻狗,
這樣他不得不調用不同的函數來註冊,而且,如果種類還在增多呢,那樣會導致向外提供的接口的增多,
註冊者因此而不得不記住那些煩瑣的名字,而這顯然沒有隻需記住AccpetAnimal這一個名字簡單)。
如果想保持這個模板函數,並將它作爲向外界提供的唯一接口,則我們需要某種方式來獲取類型T的特徵(trait),
並按照不同的特徵來採用不同的策略。這裏我們有第二個解決辦法:

 
約定所有的動物類(如class Cat,class Dog)都必須在內部typedef一個表明自己身份的類型,
作爲標識的類型如下:

struct cat_tag{}; //這只是個空類,目的是激發函數重載,後面會解釋

struct dog_tag{}; //同上


於是,所有狗類都必須像這樣:

class Dog
{
	public:

	類型(身份)標誌,表示這是狗類,如果是貓類則爲 typedef cat_tag type;
	typedef  dog_tag  type; 
	  ...
}

 

然後,動物收容組織可以在內部提供對貓狗分開處理的函數,像這樣:

// 第二個參數爲無名參數,只是爲了激發函數重載
template<class T>

void Accept(T dog,dog_tag) 
{...}

 
template<class T>

void Accpet(T cat,cat_tag) // 同上
{...}

 

 

於是先前的Accept函數可以改寫如下:

template<class T>
void Accept(T animal)  //這是向外界提供的唯一接口

{

// 如果T爲狗類,則typename T::type就是dog_tag,那麼typename T::type()就是
//創建了一個dog_tag類的臨時對象,根據函數重載的規則,這將調用Accept(T,dog_tag),
//這正是轉向處理狗的策略。如果T爲貓類,則typename T::type爲cat_tag,由上面的推導,
//這將調用Accept(T,cat_tag),即轉向處理貓的策略,typename 關鍵字告訴編譯器T::type是個類型而不是靜態成員。

Accept(animal, typename T::type()); // #1

}

所有類型推導,函數重載,都在編譯期完成,你幾乎不用耗費任何運行期成本
(除了創建dog_tag,cat_tag臨時對象的成本,然而經過編譯器的優化,這種成本可能也會消失)
就擁有了可讀性和可維護性高的代碼。“但是,等等!”你說:“traits在哪?”,
typename T::type其實就是traits,只不過少了一層封裝而已,如果像這樣作一些改進:
 

template<typename T>
struct AnimalTraits
{

typedef T::type type;

};

 

於是,#1處的代碼便可以寫成:

Accept(animal, typename AnimalTraits<T>::type());

 

效率

通常爲了提高效率,爲某種情況採取特殊的措施是必要的,例如STL裏面的copy,原型像這樣:

 

// 將[first,last)區間內的元素拷貝到以dest開始的地方

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest){ 

  // ptr_category用來萃取出迭代器的類別以進行適當程度的優化

return copy_opt(first,last,dest, ptr_category(first,dest)); 

}

 

copy_opt有兩個版本,其中一個是針對如基本類型的數組作優化的,如果拷貝發生在char數組間,
那麼根本用不着挨個元素賦值,基於數組在內存中分佈的連續性,可以用速度極快的memmove函數來完成。
ptr_category有很多重載版本,對可以使用memmove的情況返回一個空類如scalar_ptr的對象以激發函數重載。
其原始版本則返回空類non_scalar_ptr的對象。copy_opt的兩個版本於是像這樣:

 

// 使用memmove

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest, 

scalar_ptr)

{ ...}

 

// 按部就班的逐個拷貝

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest,

 non_scalar_ptr)

{ ...} 

 

其實通常爲了提高效率,還是需要分派。

 

使某些代碼能通過編譯

這或許令人費解,原來不能通過編譯的代碼,經過traits的作用就能編譯了嗎?
是的,考慮std::pair的代碼(爲使代碼簡潔,忽略大部分):

 

template <typename T1, typename T2> 

struct pair 

{

T1 first;

  T2 second;

 

// 如果T1或T2本身是引用,則編譯錯誤,因爲沒有“引用的引用”

pair(const T1 & nfirst, const T2 & nsecond) // #2

:first(nfirst), second(nsecond) { }  

}; 

 

這裏可以使用一個traits(boost庫裏面的名字爲add_reference)來避免這樣的錯誤。
這個traits內含一個typedef,如果add_reference<T>的T爲引用,則typedef T type;
如果不是引用,則typedef T& type;這樣#2處的代碼便可改成:

 

pair(add_reference<const T1>::type nfirst, 

add_reference<const T2>::type nsecond)

  ...

 

這對所有的類型都能通過編譯。

 

boost庫中的traits

boost中的Traits十分完善,可分爲如下幾大類:

 

1. Primary Type Categorisation(初級類型分類)

2. Secondary Type Categorisation(次級類型分類)

3. Type Properties(類型屬性)

4. Relationships Between Types(類型間關係)

5. Transformations Between Types(類型間轉換)

6. Synthesizing Types(類型合成)

7. Function Traits(函數traits)

 

由於其中一些traits只是簡單的模板偏特化,故不作介紹,本文僅介紹一些技術性較強的traits。
由於traits的定義往往重複代碼較多,所以必要時本文僅剖析其底層機制。所有源碼均摘自相應頭文件中,
爲使源碼簡潔,所有的宏均已展開。由於traits技巧與編譯平臺息息相關,某些平臺可能不支持模板偏特化。
這裏我們假設編譯器是符合C++標準的。在我的VC7.0上,以下代碼均通過編譯並正常工作。

 

初級類型分類

is_array (boost/type_traits/is_array.hpp)

定義

// 缺省

template<typename T> 

struct is_array

{

static const bool value=false;

};

 

// 偏特化

template<typename T,size_t N>

struct is_array<T[N]>

{

static const bool value=true;

}; 

 

註解

C++標準允許整型常量表達式作爲模板參數,上面的N就是這樣。
這也說明出現在模板偏特化版本中的模板參數(在本例中爲typename T,size_t N兩個)
個數不一定要跟缺省的(本例中爲typename T一個)相同,但出現在類名稱後面的參數個數
卻要跟缺省的個數相同(is_array<T[N]>,T[N]爲一個參數,與缺省的個數相同)。

 

使用

is_array<int [10]>::value  // true(T=int,N=10)

is_array<int>::value   // false(T=int)

 

is_class(.../is_class.hpp) 

定義

// 底層實現,原因是根據不同的編譯環境可能有不同的底層實現,我的編譯環境爲VC7.0,其他底層實現從略。

template <typename T>

struct is_class_impl

{

template <class U>

static ...::yes_type is_class_tester(void(U::*)(void));

 

template <class U> static ...::no_type is_class_tester(...);

 

// ice_and是一個元函數,提供邏輯與(AND)操作

static const bool value = 

...::ice_and<

sizeof(is_class_tester<T>(0))==sizeof(...::yes_type), // #3

      ...::ice_not<...::is_union<T>::value >::value

>::value

};

 

template<typename T>

struct is_class

{

// 所有實現都在is_class_imp中

static const bool value = is_class_impl<T>::value; 

};

 

註解

::boost::type_traits::yes_type是一個typedef:

 

typedef char yes_type; 

 

因此sizeof(yes_type)爲1.

 

::boost::type_traits::no_type則是一個struct:

 

struct no_type

{

char padding[8];

};

 

因此sizeof(no_type)爲8。

 

這兩個類型一般被用作重載函數的返回值類型,這樣通過檢查返回值類型的大小就
知道到底調用了哪個函數,它們的定義位於“boost/type_traits/detail/yes_no_type.hpp”中。

 

is_class_impl中有兩個static函數,第一個函數僅當模板參數U是類時才能夠被實例化,
因爲它的參數類型是void(U::*)(void),即指向成員函數的指針。
第二個函數具有不定量任意參數列表,C++標準說只有當其它所有的重載版本都不能匹配時,
具有任意參數列表(...)的重載版本纔會被匹配。所以,如果T爲類,則
void (T::*)(void)這種類型就存在,所以對is_class_tester<T>(0)的重載決議將是調用第一個函數,
因爲將0賦給任意類型的指針都是合法的。而如果T不是類,
則就不存在void(T::*)(void)這種指針類型,所以第一個函數就不能實例化,
這樣,對is_class_tester<T>(0)的重載決議結果只能調用第二個函數。

 

現在注意#3處的表達式:

 

sizeof(is_class_tester<T>(0))==sizeof(...::yes_type) // #3

 

按照上面的推導,如果T爲類,is_class_tester<T>(0)實際調用第一個重載版本,
返回yes_type,則該表達式求值爲true。如果T不是類,則is_class_tester<T>(0)
調用第二個重載版本,返回no_type,則該表達式求值爲false。這正是我們想要的。

 

一個值得注意的地方是:在sizeof的世界裏,沒有表達式被真正求值,
編譯器只推導出表達式的結果的類型,然後給出該類型的大小。

 

比如,對於sizeof(is_class_tester<T>(0))編譯器實際並不調用函數的代碼來求值,
而只關心函數的返回值類型。所以聲明該函數就夠了。
另一個值得注意之處是is_class_tester的兩個重載版本都用了模板函數的形式。
第一個版本用模板形式的原因是如果不那樣做,而是這樣

 

static yes_type is_class_tester(void(T::*)(void));

 

的話,則當T不是類時,該traits將不能通過編譯,原因很簡單,
當T不是類時void (T::*)(void)根本不存在。然而,使用模板時,
當T不是類時該重載版本會因不能實例化而根本不編譯,C++標準允許不被使用的模板不編譯(實例化)。
這樣編譯器就只能使用第二個版本,這正合我們的意思。

 

而is_class_tester的第二個重載版本爲模板則是因爲第一個版本是模板,
因爲在#3處對is_class_tester的調用是這樣的:

 

is_class_tester<T>(0)

 

如果第二版本不是模板的話,這樣調用只能解析爲對is_class_tester模板函數
(即第一個版本)的調用,於是重載解析也就不復存在了。

 

“等等!”你意識到了一些問題:“模板函數的調用可以不用顯式指定模板參數!”
好吧,也就是說你試圖這樣寫:

 

// 模板

template <class U>

static ...::yes_type is_class_tester(void(U::*)(void));

 

// 非模板

static ...::no_type is_class_tester(...);

 

然後在#3標記的那一行這樣調用:

 

is_class_tester(0) // 原來是is_class_tester<T>(0))

 

是的,我得承認,這的確構成了函數重載的條件,也的確令人欣喜的通過了編譯,
然而結果肯定不是你想要的。你會發現對所有類型T,is_class<T>::value現在都是0了!

 

也就是說,編譯器總是調用is_class_tester(..);這是因爲,
當調用的函數的所有重載版本中有一個或多個爲模板時,
編譯器首先要嘗試進行模板函數實例化而非重載決議,而在嘗試實例化的過程中,
編譯器會進行模板參數推導,0的類型被編譯器推導爲int(0雖然可以賦給指針,
但0的類型不可能被推導爲指針類型,因爲指針類型可能有無數種,而事實上C++是強類型語言,
對象只能屬於某一種類型),而第一個函數的參數類型
void (U::*)(void)根本無法與int匹配(因爲如果匹配了,那麼模板參數U被推導爲什麼呢?)。
所以第一個版本實例化失敗後編譯器只能採用非模板的第二個版本。結果如你所見,是令人懊惱的。
然而如果你寫的是is_class_tester<T>(0)你其實是顯式實例化了is_class_tester每一個模板函數
(除了那些不能以T爲模板參數實例化的),而它們都被列入接受重載決議的侯選單,
然後編譯器要做的就只剩下重載決議了。(關於編譯器在含有模板函數的重載版本時是如何進行重載決議的,
可參見C++ Primer的Function Templates一節,裏面有極其詳細的介紹)。

 

以上所將的利用函數重載來達到某些目的的技術在type_traits甚至整個boost庫裏多處用到。

 

初級類型分類還有:

 

is_void is_integral is_float is_pointer is_reference is_union is_enum is_function

 

請參見boost提供的文檔。

 

次級類型分類

is_member_function_pointer(.../is_member_function_pointer.hpp)

定義(.../detail/is_mem_fun_pointer_impl.hpp)

// 缺省版本

template <typename T>

struct is_mem_fun_pointer_impl

{

static const bool value = false;

};

 

// 偏特化版本,匹配無參數的成員函數

template <class R, class T  > 

struct is_mem_fun_pointer_impl<R (T::*)() >

{

static const bool value = true;

};

 

//匹配一個參數的成員函數

template <class R, class T ,  class T0>

struct is_mem_fun_pointer_impl<R (T::*)( T0) >

{

static const bool value = true;

};

 

... // 其它版本只是匹配不同參數個數的成員函數的偏特化而已,參見源文件。

 

template<class T>

struct is_mem_function_pointer

{

static const bool value = 

is_mem_fun_pointer_impl<T>::value;

};

 

註解

假設你有一個類X,你這樣判斷:

 

is_mem_function_pointer<int (X::*)(int)>::value

 

則編譯器會先將is_mem_function_pointer的模板參數class T推導爲int (X::*)(int),
然後將其傳給is_mem_fun_pointer_impl,隨後編譯器尋找後者的偏特化版本中最佳匹配項爲:

 

is_mem_fun_pointer_impl<R(T::*)(T0)>

 

其中R=int,T=X,T0=int。而該偏特化版本的::value=true。

 

次級類型分類還有:

 

is_arithmetic is_fundamental is_object is_scalar is_compound

 

請參見boost提供的文檔。

 

類型屬性

is_empty(.../is_empty.hpp)

定義

 

// 如果T是空類,那麼派生類的大小就是派生部分的大小即sizeof(int)*256

template <typename T>

struct empty_helper_t1

: public T

{                                                          

empty_helper_t1();  

  int i[256];

};

 

struct empty_helper_t2

{

int i[256];

}; // 大小爲sizeof(int)*256 

 

通過比較以上兩個類的大小可以判斷T是否爲空類,如果它們大小相等則T爲空類。
反之則不爲空。

 

這裏一個值得注意的地方是:若定義一個空類E,則sizeof(E)爲1(這一個字節是用於在
內存中唯一標識該類的不同對象。如果sizeof(E)爲0,則意味着不同的對象在內存中的位置沒有區別,
這顯然有違直觀)。然而如果有另一個非空類繼承自E,那麼這一個字節的內存就不需要。
也就是說派生類的大小等於派生部分的大小,而非加上一個字節。

 

// 這個輔助類的作用是:如果T不是類則使用該缺省版本如果T是類則使用下面的偏特化版本。
而判斷T是否爲類的工作則由上面講過的is_class<>traits來做。

template <typename T, bool is_a_class = false>  

struct empty_helper 

{

static const bool value = false;

};

 

template <typename T>

struct empty_helper<T, true> // #5

{

static const bool value = 

(sizeof(empty_helper_t1<T>) == sizeof(empty_helper_t2));

};

  

template <typename T>

struct is_empty_impl

{

// remove_cv將T的const volatile屬性去掉,這是因爲在作爲基類的類型不能有const/volatile修飾。

typedef typename remove_cv<T>::type cvt; 

  

static const bool value = 

ice_or<

      empty_helper<cvt, is_class<T>::value>::value, // #4

BOOST_IS_EMPTY(cvt)

>::value;

};

 

註解

在#4處,如果is_class<T>::value爲true(即T爲類)則
empty_helper<cvt,is_class<T>::value>::value實際決議爲empty_helper<cvt,true>,
這將採用偏特化版本#5,則結論出現。

 

否則T不是類,則採用缺省版本,結果::value爲false。

 

is_polymorphic(.../is_polymorphic.hpp)

is_plymorphic的運作機制基於一個基本事實:一個多態的類裏面會有一個虛函數表指針
(一般稱爲vptr),它指向一個虛函數表(一般稱爲vtbl)。後者保存着一系列指向虛函數
的函數指針以及運行時類型識別信息。一個虛函數表指針通常佔用4個字節(32尋址環境下
的所有指針都佔用4個字節)。反之,如果該類不是多態,則沒有這個指針的開銷。
基於這個原理,我們可以斷定:如果類X不是多態類(沒有vtbl及vptr),
則如果從它派生一個類Y,Y中僅含有一個虛函數,這會導致
sizeof(Y)>sizeof(X)(這是因爲虛函數的首次出現導致編譯器必須在Y中加入vptr的緣故)。
反之,如果X原本就是多態類,則sizeof(Y)==sizeof(X)(因爲這種情況下,
Y中其實已經有了從X繼承而來的vtbl及vptr,編譯器所要做的只是將新增的虛函數納入到vtbl中去)。

 

定義

// 當T爲類時使用這個版本

template <class T>

struct is_polymorphic_imp1 

{

typedef typename remove_cv<T>::type ncvT;

 

// ncvT是將T的const volatile修飾符去掉後的類型,因爲public後不能跟這樣的修飾符,該類裏沒有虛函數

struct d1 : public ncvT 

{                 

d1();

~d1() // throw();

char padding[256];

};

 

struct d2 : public ncvT  // 在d2中加入一個虛函數

{

d2();

 

//加入一個虛函數,如果ncvT爲非多態則會導致vptr的加入從而多佔用4字節

virtual ~d2() // throw();

char padding[256];

};

 

// 如果T爲多態類則value爲true

static const bool value = 

(sizeof(d2) == sizeof(d1));

};

 

// 當T並非類時採用這個版本

template <class T>

struct is_polymorphic_imp2  

{

// 既然T不是類,那麼就不存在多態,所以總是false

static const bool value = false;

};

 

// 這個selector根據is_class的真假來選擇判斷的方式

template <bool is_class>

struct is_polymorphic_selector

{

  // 如果is_class爲false則由is_polymorphic_imp2來判斷,這將導致結果總是false

template <class T>

struct rebind 

{

typedef is_polymorphic_imp2<T> type; // 使用_imp2

};

};

 

//當is_class爲true時使用該特化版本

template <>

struct is_polymorphic_selector<true> // #7

{

  // 如果is_class爲true,則由is_polymorphic_imp1<>來作判斷

template <class T>

struct rebind

{

typedef is_polymorphic_imp1<T> type; // 使用_imp1

};

};

 

// is_polymorphic完全由它實現

template <class T>

struct is_polymorphic_imp  

{

  // 選擇selector

typedef 

is_polymorphic_selector<is_class<T>::value> selector; // #6

 

typedef typename selector::template rebind<T> binder; // #8

 

typedef typename binder::type imp_type; // #9

 

static const bool value = imp_type::value;

};

 

註解

#6處如果T爲類,則is_class<T>::value爲true,則那一行實際上就是:

 

typedef is_polymorphic_selector<true> selector;

 

這將決議爲is_polymorphic_selector的第二個重載版本#7,其中的
template rebind將判斷的任務交給is_polymorphic_imp1,
所以#8行的binder其實就是is_polymorphic_selector<true>::rebind<T>。
而#9行的imp_type其實就是is_polymorphic_imp1<T>,結果正如預期。
如果T不是類,按照類似的推導過程,最終會推導至is_polymorphic_imp2<T>::value,這正是 false 。

 

“嗨!這太煩瑣了!”你抱怨道:“可以簡化!”。我知道,你可能會想到使用
boost::ct_if(ct_if是?:三元操作符的編譯期版本,像這樣使用:

 

typedef

ct_if<CompileTimeBool,TypeIfTrue,TypeIfFalse>::value

result;

 

 

則當CompileTimeBool爲true時result爲TypeIfTrue,否則result爲TypeIfFalse。ct_if<>的實現很簡單,
模板偏特化而已)。於是你這樣寫:

 

typedef typename boost::ct_if<

is_class<T>::value,

          is_polymorphic_imp1<T>,

          is_polymorphic_imp2<T>, 

        >::type

imp_type;

 

static const bool value = imp_type::value;

 

這在我的VC7.0環境下的確編譯通過並正常工作,但是有一個小問題:假如T不是class,
比如,T是一個int,則編譯器的類型推導會將is_polymorphic_imp1<int>賦給ct_if的第二個模板參數,
在這個過程中編譯器會不會實例化is_polymorphic_imp1<int>(或者,換句話說,
編譯器會不會去查看它的定義)呢?如果實例化了,那麼其內部的
struct d1 : public ncvT會不會也跟着實例化爲struct d1:public int,
如果是這樣,那麼將會有編譯期錯誤,因爲C++標準不允許有 public int 這樣的東西出現。
事實上我的編譯器沒有報錯,即是說它並沒有去查看is_polymorphic_imp1<int>的定義。

 

而C++標準實際上也支持這種做法。但boost庫中的做法更爲保險,也許是爲了應付一些老舊的編譯器。

 

類型屬性traits還有:

 

alignment_of is_const is_volatile is_pod has_trivial_constructor等

 

類型間關係

is_base_and_derived(boost/type_traits/is_base_and_derived.hpp)

定義

template<typename B, typename D>

struct bd_helper

{

template<typename T>

static type_traits::yes_type check(D const volatile *, T); 

  

static type_traits::no_type check(B const volatile *, int);

};

 

template<typename B, typename D>

struct is_base_and_derived_impl2

{

struct Host

{

// 該轉換操作符當對象爲const對象時才起作用

operator B const volatile *() const;

 

operator D const volatile *();

};

 

static const bool value = 

sizeof(bd_helper<B,D>::check(Host(), 0)) // #10

== sizeof(type_traits::yes_type);

}; 

 

以上就是is_base_and_derived的底層機制。下面我就爲你講解它所仰賴的機制,假設有這樣的類繼承體系:

 

struct B {};

struct B1 : B {};

struct B2 : B {};

  

struct D : private B1, private B2 {}; 

 

將D*轉換爲B1*會導致訪問違規,因爲私有基類部分無法訪問,但是後面解釋了這爲什麼不會發生。

 

首先來看一些術語:

 

SC  - Standard Conversion

UDC - User-Defined Conversion

 

一個user-defined轉換序列由一個SC後跟一個UDC後再跟一個SC組成。
其中頭尾兩個SC都可以爲到自身的轉換(如:D->D),#10處將一個缺省構造的Host()交給bd_helper<B,D>::check函數。

 

對於static no_type check(B const volatile *, int),我們有如下可行的隱式轉換序列:

 

Host -> Host const -> B const volatile* (UDC)

或

Host -> D const volatile* (UDC) -> B1 const volatile* / B2 const volatile* -> B const volatile* (SC)

 

而對於static yes_type check(D const volatile *, T),我們則有如下轉換序列:

 

Host -> D const volatile* (UDC)

 

C++標準說,在重載決議中選擇最佳匹配函數時,只考慮標準轉換(SC)序列,
而這個序列直到遇到一個UDC爲止,對於第一個函數,將Host -> Host const與Host -> Host比較,
顯然選擇後者。因爲後者是前者的一個真子集。因此,去掉第一個轉換序列我們得到:

 

C -> D const volatile* (UDC) -> B1 const volatile* / B2 const volatile* -> B const volatile* (SC)

 

vs.

 

C -> D const volatile* (UDC)

 

這裏採用選擇最短序列的原則,選擇後者,這表明編譯器甚至根本不需要去考慮向B轉換的多重路徑,
或者訪問限制,所以轉換二義性和訪問違規也就不會發生。結論是如果D繼承自B,則選擇yes_type check()。

 

如果D不是繼承自B,則對於static no_type check(B const volatile *, int)編譯器的給出的轉換爲:

 

C -> C const -> B const volatile*(UDC)

 

對於static yes_type check(D const volatile *, T)編譯器給出:

 

C -> D const volatile* (UDC)

 

這兩個都不錯(都需要一個UDC),然而由於
static no_type check(B const volatile *, int)爲非模板函數,
所以被編譯器選用。結論是如果D並非繼承自B,則選擇no_type check()。

 

另外,在我的VC7.0環境下,如果將Host的operator B const volatile *() const 的 const 拿掉,
則結果將總是false。

 

可惜這樣的理解並不屬於我,它們來自boost源代碼中的註釋。

 

is_convertible(boost/type_traits/is_convertible.hpp)

定義

template< typename From >

struct does_conversion_exist

{

template< typename To >

struct result_

{

  // 當不存在從From到To的任何轉型時調用它

static no_type _m_check(...);  

 

// 只要轉型存在就調用它

static yes_type _m_check(To);

 

// 這只是個聲明,所以並不佔用空間,且沒有開銷。

static From _m_from; 

      

enum 

{

value = 

sizeof( _m_check(_m_from) ) == sizeof(yes_type);

};

};

};

 

// 這是個爲void準備的特化版本,因爲不能聲明void _m_from,只有void可以向void“轉換”

template<>

struct does_conversion_exist<void>

{ 

  template< typename To >

struct result_

{

enum { value = ::boost::is_void<To>::value };

  };

};

 

// is_convertible完全使用does_conversion_exist作底層機制,所以略去。

 

註解

does_conversion_exist也使用了與is_class_impl一樣的技術。所以註解從略。
該技術最初由Andrei  Alexandrescu發明。

 
最後,Transformations Between Types(類型間轉換),Synthesizing Types(類型合成),
Function Traits(函數traits)的機制較爲單純,請自行參考boost提供的文檔或頭文件。


參考文獻:1 2 3 












發佈了69 篇原創文章 · 獲贊 8 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章