歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

C++自動化(模板元)編程基礎與應用

#if 0   大家好,在這一類的文章中將會系統的介紹模板元編程的相關基礎知識。最主要的是這類文章的目的就是讓不熟悉模板元的C++迷們盡快的進入到模板元的奇妙世界裡面來,所以每一篇文章都將只會討論一個話題,盡可能的把這個話題說清楚明白就可以了。    好了,言歸正傳。大家都知道C++是一們計算機語言,這一點也沒錯,但是你知道C++裡面還包含了另外一種子語言麼?呵呵,恐怕知道的人就不多了,會用的人就更少了。但是既然經過了這麼多年的發展,C++語言裡面出現了這種子語言(模板)自然有它的根源的,這一點並不是本文中將要討論的,如果想了解C++的發展例程,可以參見相關的文獻。    在本文中將要說明的問題是:為什麼說C++模板是一種語言呢?    為了回答這個問題,首先需要考慮一下什麼是計算機語言,關於這個精確的定義,很多的計算機基礎教程上都有,在這裡我給出一種比較窄的定義:    能夠在計算機上表達選擇結構,循環結構,同時能夠進行進行整數的四則運算的體系就是一種計算機語言。    很顯然,C++自然是一種計算機語言了,還有Basic,Fortran,Pascal等等都是計算機語言。之所以討論這麼多的概念問題是為了說明:如何證明C++的模板語法是一種計算機語言。又因為模板是C++語言的一個元素,所以又可以將C++模板語法稱為C++的二級語言或者子語言。在本文中將會通過使用模板分別實現整數四則運算,選擇結構以及循環結構來證明C++模板語法構成了一個完整的計算機語言。    另外特別值得注意的是,因為C++的模板語言是在編譯器編譯的時候完成的,所以又稱為靜態語言,通常的C++語言又稱為動態語言或者運行時語言。正是因為模板語言是在編譯期完成的,所以我們可以借助於這種編譯期的計算實現代碼自動生成的目的,從而實現C++自動化編程。這是後續的文章中會詳細討論的。    首先看看,模板是如何完成編譯期四則計算的。  #endif#ifdef CODE1//編譯期四則計算的示例代碼#include template strUCt Add { enum{value = i+j}; };template struct Sub { enum{value = i-j}; };template struct Mul { enum{value = i*j}; };template struct Div { enum{value = i/j}; };int main(){        std::cout << "4+2=" << Add<4,2>::value << std::endl;        std::cout << "4-2=" << Sub<4,2>::value << std::endl;        std::cout << "4*2=" << Mul<4,2>::value << std::endl;        std::cout << "4/2=" << Div<4,2>::value << std::endl;        //為了證明上面的計算是在編譯期進行的,我們編寫下面的代碼測試        //將模板值作為數組定義時使用的參數就可以證明是在編譯期執行的計算:)        int a[Add<4,2>::value];//這麼定義並沒有錯        int b[Sub<4,2>::value];//這麼定義並沒有錯        int c[Mul<4,2>::value];//這麼定義並沒有錯        int d[Div<4,2>::value];//這麼定義並沒有錯        std::cout << sizeof(a)/sizeof(int) << std::endl;        std::cout << sizeof(b)/sizeof(int) << std::endl;        std::cout << sizeof(c)/sizeof(int) << std::endl;        std::cout << sizeof(d)/sizeof(int) << std::endl;        return 0;}#endif//CODE1//////////////////////////////////////////////////////////////////////////////////程序運行結果如下所示:/*******************************************************************************4+2=64-2=24*2=84/2=26282*******************************************************************************/////////////////////////////////////////////////////////////////////////////////#if 0    從代碼CODE1中可以看出使用整型模板參數的模板是可以實現編譯期計算的,在這裡,證明了這個計算過程是在編譯期完成的。    好了,現在看看如何使用C++模板實現選擇結構,見代碼CODE2:#endif#ifdef CODE2//編譯期實現選擇的示例代碼#include template struct IF{        typedef Then result;//將Then類型作為條件為真的返回值(返回值為類型)};templatestruct IF{        typedef Else result;//將Else類型作為條件為假的返回值(返回值為類型)};//為了測試這個IF選擇結構,需要下面的兩個類型定義:struct True {static void Print(){std::cout << "真" << std::endl;}};struct False{static void Print(){std::cout << "假" << std::endl;}};int main(){        IF<1==1,True,False>::result::Print();        IF<1!=1,True,False>::result::Print();        return 0;}#endif//CODE2//////////////////////////////////////////////////////////////////////////////////程序運行結果如下所示:/*******************************************************************************真假*******************************************************************************/////////////////////////////////////////////////////////////////////////////////#if 0    從CODE2中可以看出,這裡操作的對象是類型,而CODE1中的操作對象是整數,到了這裡可以總結如下:C++模板元編程中的操作對象只有兩種,一種是整形數,包括bool,char,int,long,(signed unsigned)都可以,它們都可以當作整數使用,賦值和保存結果的方式都是通過枚舉變量來實現;另一種就是類型了,賦值和保存結果都是通過typedef來實現的。例如CODE2中將IF的選擇結果以Result的方式保存作為結果就是通過typedef實現的。    再來看看循環結構:#endif#ifdef CODE3//編譯期實現循環的示例代碼#include //為了簡單采用一個階乘作為例子,因為如果用普通的C++語法來實現階乘函數的話需要//一個循環結構的,這裡采用模板遞歸的方式實現了這種階乘,也就實現了一種特殊的//循環結構。template struct Power {        enum{value=n*Power::value}; //循環遞歸過程};template<> struct Power<0> {        enum{value=1}; //0的階乘是1,也是循環的終止條件};int main(){        int a[Power<5>::value];//同樣用數組參數來判斷是否在編譯期完成計算        std::cout << sizeof(a)/sizeof(int) << std::endl;        return 0;}#endif//CODE3//////////////////////////////////////////////////////////////////////////////////程序運行結果如下所示:/*******************************************************************************120*******************************************************************************/////////////////////////////////////////////////////////////////////////////////#if 0    從CODE3中我們可以看出Power是通過模板遞歸的方式實現循環的,而且這個循環過程是在編譯期完成的。到了這裡可以總結出:C++模板元編程中實現循環的方式只有一種,那就是模板遞歸實現循環。雖然這裡的Power的循環不怎麼直接,但是它確確實實是一個循環結構,只不過是一個非常特殊的循環結構。實際上采用模板遞歸的方法可以實現普通C++語法裡面的for循環,while循環,do-while循環這些通用的循環結構。    到目前為止,已經成功的證明了C++模板是一個完整的計算機語言。既然是一門語言,當然可以做許許多多的事情,這就在於每個人的發揮了。好了,在本文的最後給出一個通用的LOOP循環作為本文的結束,這個LOOP循環可以進行簡單的循環算法設計了,下面的例子中將會說明這一點:#endif#ifdef CODE4#include template void print(){//這裡的n是編譯期的結果,可以用來定義數組的        int a[n+1];//這麼做是為了證明n是編譯期常量,同時避免出現零個元素的數組        std::cout << sizeof(a)/sizeof(int)-1 << " " ;}template struct LOOP{        static void execute(){LOOP::execute();print();}};template <>struct LOOP<0>//循環終止條件{        static void execute(){print<0>();}};int main(){        LOOP<5>::execute();        return 0;}#endif//CODE4//////////////////////////////////////////////////////////////////////////////////程序運行結果如下所示:/*******************************************************************************0 1 2 3 4 5 *******************************************************************************/////////////////////////////////////////////////////////////////////////////////#if 0    從CODE4中可以看出,這個靜態LOOP循環是一個相對來說通用的循環代碼,只需要將自己的功能代碼寫入到一個函數(print)中就可以實現靜態循環了,更重要的是,這個靜態LOOP循環實現了靜態代碼和動態代碼的連接,因此用途更加廣泛,主要可以用來產生代碼。關於如何產生代碼,以及如何使用將是本類文章的後續文章討論的內容。




 

#if 0    在上一篇文章的最後提到了一個相對來說通用一點的LOOP循環,下面還是將上一篇文章中的LOOP循環代碼復制如下:#endif#ifdef CODE1#include template void print(){//這裡的n是編譯期的結果,可以用來定義數組的        int a[n+1];//這麼做是為了證明n是編譯期常量        std::cout << sizeof(a)/sizeof(int)-1 << " " ;}template struct LOOP{        static void execute(){LOOP::execute();print();}};template <>struct LOOP<0>//循環終止條件{        static void execute(){print<0>();}};int main(){        LOOP<5>::execute();        return 0;}#endif//CODE1//////////////////////////////////////////////////////////////////////////////////程序運行結果如下所示:/*******************************************************************************0 1 2 3 4 5 *******************************************************************************/////////////////////////////////////////////////////////////////////////////////#if 0    現在所需要考慮的問題是怎麼將上面的print模板函數書寫得更通用些,因為每定義一個新的print函數就需要重新書寫兩個LOOP模板,為了避免這種重復代碼得書寫,在此很容易想到的是將這個print模板函數作為模板參數傳遞,好了讓我們開始新的嘗試吧。具體得示例代碼如下所示:#endif#ifdef CODE2#include //下面的兩個模板就比CODE1裡面的LOOP模板通用多了template  

#if 0    在前面的兩章裡面討論了C++模板元作為C++的一門二級語言的問題,並給出了常用的程序設計語言的語素的實現,是一個完備的體系。總的來說,前面的章節裡面是采用了下面的方法來實現這些語素的:    (1)整數計算結果通過enum變量進行保存    (2)類型計算結果通過typedef進行保存    (3)?:運算符可用來實現靜態整型表達式的選擇功能    (4)模板特化可用來實現靜態類型表達式的選擇功能    (5)模板遞歸可用來實現靜態循環,循環變化元素只能夠是整數    (6)通過整數可以映射到類型,所以循環變化元素也可以間接為類型    這一章裡面我們將要討論另外的問題,所采用的方法也是這些方法。那麼本文將要討論的問題是:    如何實現類型循環,也就是上面總結出來的第(6)種技巧。    關於這一點的討論,我認真參考了<>一書的Typelist,在本文中將會以cons來表達類型列表的概念,並對<>一書的Typelist相關的操作進行精簡,得到我們將會在生成代碼的過程中使用的模板元函數。不用的根本不會考慮,所以為了使撤銷和重做庫盡可能的獨立些,所以我不采用Loki庫,這樣使得該撤銷和重做庫的安裝比較簡單。    為此,首先實現一個類型串類型名叫cons,代碼如下:    #endif#ifdef CODE_NOTE//cons的實現,采用和STL類似的類型命名方式template struct cons{        typedef FirstType  first_type;        typedef SecondType second_type;};//有了上面的cons的實現,現在就可以很容易的將類型放入到這個串中了:typedef cons  

#if 0 這一章,我們將要開始的討論C++裡面的代碼生成技術。說起代碼生成技術,實際上這並不是C++的專利,作為C++子集的C語言早就已經使用了一定的代碼生成技術,這就是C宏。我想C宏大家應該非常熟悉了吧,特別是能夠實現帶參數的宏使得大量的庫利用這種技術來生成那些格式重復的代碼,例如:微軟的MFC庫,跨平台的GUI庫wxWidget,Boost庫等等都使用了C宏。雖然C宏在這些庫裡面扮演了非常重要的角色,並且仍將扮演非常重要的角色,但是也不得不說:C宏存在著很大的問題。最基礎的就是類型不安全性,這也是C++裡面出現模板語素的一個比較重要的原因。更重要的是使用C宏生成的代碼僅僅只是實現了簡單的格式填空能力,並不能表達特定的算法。正是C宏的表達設計思想的不足限制了C宏的使用范圍。 說起C++模板的代碼生成能力,說起來這也是一種巧合,自從90年代初期第一個C++模板元程序(用來在編譯期輸出質數)被發現以來,C++迷們對模板元程序的研究就熱鬧起來了,並出現了大量的關於C++模板元程序的文獻。在這裡我所介紹的模板元代碼生成技術主要參考了<>一書的GenScatterHierarchy結構,並對這種結構進行了擴展應用,采用了前面的LOOP靜態循環實現對這種結構生成的代碼的操作,從而完成了一個C++普通類的自動生成過程。所謂的C++普通類指的是一般的手工直接編寫的一個類,這種類通常包含成員變量,生成成員變量的過程可以由GenScatterHierarchy結構完成,但是僅僅有了成員變量還不能成為一個C++類,或許成為結構體更合適;另外普通類一般還包含了成員函數,這種成員函數的自動生成就不能通過Loki庫來實現自動生成了,雖然Loki庫的GenLinearHierarchy結構可以生成函數接口,但是函數體裡面的內容就不能夠隨心所欲的編寫了。這後面的一點正是在本文中將要進行詳細討論的。 好了,現在我們來分析前面的章節中介紹的模板元技術中已經蘊涵的代碼生成技術。實際上LOOP靜態循環中已經實現了靜態函數的自動生成,也就是說,編譯器在編譯的時候確確實實是看到了循環所產生的所有的靜態函數,而並不是運行的時候進行的函數遞歸調用。下面我們來看看C++裡面的多繼承現象和參數化繼承現象:#endif#ifdef CODE_NOTE//多繼承現象class Base1{};class Base2{};class Base3{};class Derived:public Base1,public Base2,public Base3{};//模板參數化繼承現象:template Base{};class Derived:public Base,public Base,public Base{};#endif//CODE_NOTE#if 0 從上面的多繼承和參數化的多繼承我們可以得到什麼靈感呢?如果沒有,那麼再考慮一下上一章中所介紹的類型串類型,^_^這時候有沒有靈感了呢?呵呵,實際上上面的代碼中的參數化多繼承的基類就是一個類型遍歷過程,針對每一個類型,用Base包裹住每一個類型並作為Derived類的基類,這樣就可以生成一個自己定制的類了。如果能夠使這個過程自動化,那麼我們就可以認為代碼被自動生成了。 現在考慮一下上面的自動化過程所需要的輸入和輸出分別是什麼: 輸入:一個cons類型串記錄所有的需要的類型,一個包裹模板類 輸出:生成一個由所有的cons類型串中的類型作為模板參數的包裹類作為基類的類 這樣如果在包裹類裡面定義了一個模板參數類型的成員變量,那麼生成的類中就有所有的這些類型的變量,也就是說這些變量都成了生成的類的成員變量。 好了,說到這裡,我們來看看具體的實現過程是怎樣的:#endif#ifdef CODE_NOTE//下面是實現代碼自動生成的模板元函數,主要參考了Loki的代碼//為了撤銷和重做庫的獨立性,將該功能從Loki庫中提取出來template

 

#if 0 在上一篇文章裡面討論了C++裡面的代碼生成技術,使用的是scatter,不過上一篇文章裡面也提到了,前一篇文章裡面討論的代碼生成模板scatter使用的類型串絕對不允許重復。其實上一篇中的scatter使用由重復的類型的類型串也是能夠正常生成代碼的,不過產生的代碼卻不能將類型重復的變量分辨出來,這樣生成的代碼就沒有了什麼實際意義,所以在這一章中將要解決的問題是:重新編寫一個可以使用重復類型的類型串生成代碼,並且能夠采用一定的方法將這些生成的變量分辨出來。 那麼該如何編寫這裡需要的代碼呢?上一章裡面的scatter見下面的代碼:#endif#ifdef CODE_NOTEtemplate

 

#if 0 在上一章裡面討論了代碼的自動生成機制,經常會遇到根據不同的類型選擇不同的操作的情況,也就是靜態分派的問題。這就需要有一種機制用來識別不同的類型,在本章裡面將會系統的討論C++裡面可用的類型識別問題。 最常見的有下面幾種: (1)根據模板參數需要精確匹配類型來識別類型 (2)根據隱式自動轉型來判斷類型是否可以自動轉型,從而可以判斷是某個基類的派生類。 (3)給每一個類型都追加一個額外的模板參數用來表示不同的類型,一般都是用不同的數字映射為不同的類型來實現類型識別的,前面討論的可以有重復類型的scatter代碼產生器就采用了這種方法來識別不同的類型的。 在這一章裡面將會分別討論上面的三種情況的一般應用: #endif#ifdef CODE1//g++ -DCODE1 thisfile.cpp//采用方法(1)來識別不同的類型#include #include #include template struct traits;template <> struct traits {static const char*name(){return "char ";}};template <> struct traits {static const char*name(){return "int ";}};template <> struct traits {static const char*name(){return "short ";}};template <> struct traits {static const char*name(){return "long ";}};template <> struct traits {static const char*name(){return "float ";}};template <> struct traits {static const char*name(){return "double";}};template <> struct traits{ static const char*name(){return "std::string";}};template <> struct traits



Copyright © Linux教程網 All Rights Reserved