遞迴之美 - Loki庫TypeList原始碼剖析 (轉)
遞迴之美 - Loki庫TypeList原始碼剖析
鄧 輝
TypeList概觀
提起List,想必大家都不會陌生,它是一個元素的集合,並且提供了一些對該集合進行操作的方法,比如:計算集合中元素的個數、向集合中新增元素、獲取給定處的元素等。我們所熟知的List中的元素一般都是例項化後的值,相關的操作也都是在執行期間進行的。本文將要剖析的List和上述的List在某種意義上比較相象,不過它所包含的元素都是型別(type),相關的操作是在編譯期間進行的。
本文將要講述的TypeList取自Andrei Alexandrescu的力作《Modern C++ Design》一書相關的Loki庫,關於《Modern C++ Design》,C++的愛好者想必不會陌生,在該書中,作者向我們展現了C++設計的全新思維,把C++的表達能力發揮到了極至,而Loki庫正是這種思維的具體表現。TypeLsit是Loki庫中最為基礎、核心的,理解了TypeList就具備了觀賞這道C++新景觀的基礎。
下面我們先來看看如何定義一個TypeList,這樣可以對於TypeList先有一個感性的認識。
typedef TYPELIST_3(char, int, long) MyTypeList;
定義了一個具有三個元素的TypeLsit,這三個元素分別為:char、int、long。TYPELIST_3為Loki庫中提供的用於定義TypeList的工具,我們會在本文的後面進行介紹。
::Loki::Length
計算MyTypeList中元素的個數,結果為3。
typedef ::Loki::TypeAt
獲取MyTypeList中第1個元素(從0開始),此時MyType就是int。
typedef ::Loki::Append
向MyTypeList中在新增一個元素:float,結果為MyTypeList1。此時::Length
好了,先介紹這麼多,下面我們將介紹TypeList實現的一些相關的背景知識,包括:遞迴的基本概念、模板的特化(tempalte specilization)、模板的偏特化(template partial specilization)以及型別萃取技術(type traits)。
TypeList相關技術
遞迴概述
對於遞迴,大家肯定都不陌生,使用遞迴方法給出的解決方案總是顯得非常的優雅、簡潔。不過遞迴方法所適合解決的問題應該符合下面的條件:
- 一個問題的解決依賴於一個較小規模的同樣的問題的解決
- 必須有一個明確的結束條件
- 這個結束條件是可達的
如果一個問題符合上述的三個條件,我們就可以使用遞迴的方法。首先我們定義一套解決問題的規則,接著縮小問題的規模並應用同樣的規則直到達到結束條件,然後結果層層返回直到原始問題。著名的漢諾塔問題就是一個典型的遞迴問題,如果不使用遞迴方法,解決漢諾塔問題就會顯得非常的複雜,晦澀。
我們在使用遞迴方法設計時,這個遞迴過程的總是在執行期間進行的。本文所介紹的TypeList的實現中,遞迴的是在編譯期間進行的,那麼在編譯期間如何定義每次遞迴的返回結果,如何定義結束條件呢?其中主要使用了下面將要介紹的模板特化、偏特化以及型別萃取技術。
模板特化和偏特化(template specilizaiton、partial specilization)
什麼是模板的特化、偏特化呢?大致的意思為:如果一個template擁有一個或者一個以上的template引數,我們可以針對其中一個或者多個引數進行特化處理(如果全部進行特化處理就是全特化,否則就是偏特化,切記:模板只能進行全特化,不能進行部分特化)。也就是說,我們可以提供一個特別版本,符合泛化條件,但是其中某些(全部)template引數已經由實際型別或者數值取代。
假設我們有一個class template定義如下:
template
class C { … };
對於模板的偏特化,剛剛接觸可能會存在一些誤解:以為模板的偏特化版本一定是對template引數U或者V或者T(或者它們的任意組合)指定某個引數值。其實不是這樣的,所謂模板的偏特化是指另外提供一份template的定義,它的具體含義可以和通用的template定義版本無關。在一個偏特化版本中,template 引數的個數並不需要吻合通用的 template 中的個數。然而,出現於於class 名稱之後的引數個數必須吻合通用的 template 的引數個數。下面舉一個簡單的例子進行說明:
template
class C { … };
這個泛型版本允許T為任意的型別。它的一個偏特化版本如下:
template
class C
這個偏特化版本僅適用於T為原生指標型別的情況。
當我們使用C
型別萃取(type traits)
型別萃取技術是泛型中的一個常用技術,它的思維核心為:把一系列與型別相關的性質包裹於單一的class 之內,這樣我們就可以在編譯期間獲取一些所需要的和該型別相關的東西。其實這個思路就是領域一句著名的諺語:“任何事情都可以透過新增額外的中間層次得以解決”的又一次體現。透過把一系列想得到的型別相關的資訊封裝在另外一個型別定義中,這樣就可以以一致的方式來對這些型別進行處理,提供了強大的可複用性和靈活性。
型別萃取技術一般都和模板的特化、偏特化技術結合在一起運用,這樣它們就可以互相補充發揮出巨大的威力。下面簡單舉一個例子來了解一下型別萃取技術。
我們來看看Boost庫中一個簡單的template
template
struct is_pointer
{ static const bool value = false; };
template
struct is_pointer
{ static const bool value = true; };
一個簡單的示例如下:
template
void Func(T param)
{
if (is_pointer
// do something
}
else {
//do something
}
…
}
透過型別萃取技術,我們就可以在編譯期間保留每次遞迴的結果,供遞迴返回時使用。
關於這些技術的更多、更深入的介紹,請讀者自行參考相關資料,不在此贅述。在下面的原始碼剖析中,讀者將會看到這些技術的實際運用。
TypeList實現剖析
有了上述的背景知識,下面我們就來揭開TypeList的神秘面紗,走進TypeList的原始碼世界。首先,我們來看一下TypeList的定義。
TypeList定義
為了能夠一致的進行TypeList的操作,在Loki庫中定義了一個空型別NullType來標記一個TypeList的結束,NullType和TypeList的定義如下:
class NullType { };
template
struct Typelist
{
typedef T Head;
typedef U Tail;
};
對於規範型TypeList的定義採用了一種尾遞迴的方法:
- NullType是規範的TypeLsit
- 如果T是規範的TypeList,那麼對於任意原子型別U,TypeList是規範的TypeList
Loki庫中所採用的TypeList均為規範型的TypeList,這樣可以在不減少靈活性的前提下簡化對於TypeList的操作。本文後面所指的TypeList均為規範型的。
如何定義一個TypeList呢?比如:包含:char、int、long三個元素的TypeLsit。根據上面的定義,可以得到如下的定義形式:
TypeList
// 然編譯器會認為是 “>>” 運算子
這樣的定義方法顯得比較麻煩、羅嗦,為了簡化對於TypeList的使用,Loki庫中採用了宏定義的方式對於大小在1~50的TypeList進行了預定義:
#define TYPELIST_1(T1) ::Loki::Typelist
#define TYPELIST_2(T1, T2) ::Loki::Typelist
#define TYPELIST_3(T1, T2, T3) ::Loki::Typelist
…
依此類推。
這樣使用者在使用起來就會比較方便一些。
TypeList典型操作實現
瞭解了TypeList的定義,這裡我們將對於TypeList相關的三個典型操作(Length、TypeAt和Append)的實現進行詳細的剖析。掌握了這幾個典型的操作,再學習其他的操作就會變得非常的容易。
我們將透過一個例項進行講解,來看一下編譯器實際的運作過程。我們定義了一個包含兩個元素:int以及long的TypeList。
typedef TYPELIST_2(int, long) MyTypeList;
此時,編譯器會根據TypeList的定義方式產生如下的型別定義結果:
struct TypeList
{
typedef long H;
typedef NullType T;
};
struct TypeList
{
typedef int H;
typedef TypeList
};
Length的實現 - 獲取TypeList中的元素個數:
template
// 的話,會產生一個編譯期錯誤
template <> struct Length
{ // 模板特化和型別萃取技
// 術
enum { value = 0 };
};
template
struct Length< Typelist
{
enum { value = 1 + Length::value };
};
當透過Length
struct Length
{
enum { value = 1 + Length
};
struct Length
{
enum { value = 1 + Length
};
根據Length結束條件的定義可知,Length
TypeAT的實現 - 獲取給定位置處的元素
template
// 的型別不是TypeLsit
// 的話,會產生一個編譯期錯誤
template
struct TypeAt
{ // 返回TypeList中的第一個元素
typedef Head Result;
};
template
struct TypeAt
{ // 用是告訴編譯器其後的實體是型別。
typedef typename TypeAt
};
下面看看使用TypeAt
struct TypeAt
{
typedef long Result;
};
struct TypeAt
{
typedef TypeAt
};
很明顯typedef TypeAt
Append的實現 - 在TypeList的末尾新增一個元素
template
// 的型別不是TypeLsit
// 的話,會產生一個編譯期錯誤
template <> struct Append
{
typedef NullType Result;
};
template
{
typedef TYPELIST_1(T) Result;
};
template
struct Append
{
typedef Typelist
Result;};
template
struct Append
{ // 用是告訴編譯器其後的實體是型別。模板的偏特
// 化
typedef Typelist
typename Append
Result;
};
同樣的,讓我們來看看當使用Append
struct Append
{
typedef TYPELIST_1(float) Result;
};
struct Append
{
typedef TypeList
};
struct Append
{
typedef TypeList
};
經過簡單的替換,Append
typedef TypeList
等於:
typedef TypeList
等於:
typedef TypeList
也就是:
TYPELIST_3(int, long, float) ;等同於在原有TypeList的末尾新增了一個新元素float。不用多說了,這裡型別萃取技術同樣發揮了巨大的作用。
結束語
本文對於TypeLsit的實現進行了剖析,相信讀者朋友對於TypeList含義以及實現手法已經有所掌握。那麼TypeLsit到底有什麼用處呢?原始碼面前,了無秘密,掌握了TypeList,就掌握了全面理解Loki庫的鑰匙。在Loki庫中,你將會看到Abstract Factory、Visitor模式這些泛型元件是如何在TypeLsit的基礎上搭建起來的。背起你的行囊,拿起這把鑰匙,趕快踏上你的“尋寶”之路吧(Loki庫的可以從)。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992500/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 程式碼之美---遞迴之美遞迴
- 快速排序(遞迴及非遞迴演算法原始碼)排序遞迴演算法原始碼
- ZStack原始碼剖析之核心庫鑑賞——Defer原始碼
- golang 遞迴自己,輸出自己的原始碼Golang遞迴原始碼
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】Vector原始碼剖析Java原始碼
- 【Java集合原始碼剖析】HashMap原始碼剖析Java原始碼HashMap
- 【Java集合原始碼剖析】Hashtable原始碼剖析Java原始碼
- 【Java集合原始碼剖析】TreeMap原始碼剖析Java原始碼
- go-micro之原始碼剖析: RegistryGo原始碼
- spark 原始碼分析之十三 -- SerializerManager剖析Spark原始碼
- 【Java集合原始碼剖析】LinkedList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】LinkedHashmap原始碼剖析Java原始碼HashMap
- 函式之遞迴函式遞迴
- 遞迴與goto (轉)遞迴Go
- 從原始碼層面深度剖析Spring迴圈依賴原始碼Spring
- 【C++】翻轉二叉樹(遞迴、非遞迴)C++二叉樹遞迴
- epoll–原始碼剖析原始碼
- HashMap原始碼剖析HashMap原始碼
- Alamofire 原始碼剖析原始碼
- Handler原始碼剖析原始碼
- Kafka 原始碼剖析Kafka原始碼
- TreeMap原始碼剖析原始碼
- SDWebImage原始碼剖析(-)Web原始碼
- Boost原始碼剖析--原始碼
- JavaScript專題之遞迴JavaScript遞迴
- angular原始碼剖析之Provider系列--QProviderAngular原始碼IDE
- angular原始碼剖析之Provider系列--CacheFactoryProviderAngular原始碼IDE
- Spring原始碼剖析9:Spring事務原始碼剖析Spring原始碼
- 遞迴和尾遞迴遞迴
- 原始碼分析:CyclicBarrier 之迴圈柵欄原始碼
- JavaScript演算法之遞迴JavaScript演算法遞迴
- Flutter 原始碼剖析(一)Flutter原始碼
- 全面剖析 Redux 原始碼Redux原始碼
- vue原始碼剖析(一)Vue原始碼
- Kafka 原始碼剖析(一)Kafka原始碼
- Thread原始碼剖析thread原始碼