初探模板元編程
關於模板元編程的知識也有所了解,相關的書籍也看過幾本,但是至今還沒有親手寫過一個模板元程序,原因就是沒有一個合適的機會應用模板元編程技術,今天在CSDN上看見一個帖子,定義常量字符串
char *p="Hello,Word!";
既然是常量字符串,應該可以在編譯期知道p的長度,在編譯期間如何得到?
這個問題,我首先想到用模板元編程來實現,於是嚐試編寫一個。
真正用模板元編程時,才發現自己完全沒有思路,不知道如何解決。於是下定決心學好模板元編程,這個問題就是我學習的動力,希望以後能夠使用模板元編程解決這個問題,或者明確下來不能解決這個問題。
今天寫了個例子,才發現自己對模板元的理解很膚淺,常常被一些弱智的問題打敗,對於任何想新學模板元編程的同學可能都會碰到,記錄下來和大家分享一下,希望對別人有所幫助。
我們從模板元編程的"Hello word"開始,使用模板元編程求一個數的階乘。這是我寫的第一個例子
#include using namespace::std; template class B { public: int num() { return I * B().num(); } }; template<> class B <1>{ public: int num() { return 1; } }; int main() { int i = B<5>().num(); cout << i << endl; }編譯運行,輸出正確結果,啊哈,這麼簡單。但是很遺憾,這不是一個模板元編程,而是一個泛型編程而已。模板元編程是運用編譯器在編譯階段程序設計,而上麵是類實例,通過調用對象方法來實現的,大忌啊!所以要仔細區分編譯期和運行期的區別,下麵有必要列舉C++中常用編譯期操作和運行期操作。
編譯期
宏
typedef影射
static類型變量和函數
const 類型變量
=,:?,-運算符
enum
運行期
對象使用
函數調用
變量賦值
操作變量時&,+=,++,--等運算符。
所以,如果想實現模板元編程,必須要把握的是一定要在編譯期完成程序,而不是在程序的運行期,仔細區分運行期和編譯期是模板元編程的第一步。
那麼根據以上要求,一種比較正確的方法是,在下麵的例子中融合了enum和const static組合兩種方法
template class B { public: enum {value=I*B::value}; static const int value1=I*B::value1; }; template<> class B <1>{ public: enum {value=1}; static const int value1=1; }; int main() { int a[B<5>::value]; int b[B<5>::value1]; int i = B<5>::value; int j = B<5>::value1; cout << i << endl; cout << j << endl; }這裏定義a,b數組,是為了驗證B<5>::value和B<5>::value1(value1是采用靜態常量的方式,value是枚舉類型,兩者的區別可以理解為靜態常量占用運行期內存空間,而枚舉類型不會)編譯後是常量(對windows平台有效),當然也可以通過查看匯編代碼來發現,下麵是我們上麵這段代碼的關鍵匯編代碼
main: .LFB1442: pushl %ebp .LCFI4: movl %esp, %ebp .LCFI5: subl $984, %esp .LCFI6: andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp movl $120, -972(%ebp) 注釋1 movl $120, -976(%ebp) 注釋2 subl $8, %esp pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ subl $12, %esp pushl -972(%ebp) pushl $_ZSt4cout .LCFI7:
上述代碼中注釋1是int i = B<5>::value;的匯編代碼,注釋2是int i = B<5>::value1;得匯編代碼,通過上述匯編代碼,我們看到編譯後,B<5>::value或者B<5>::value1直接就為120,沒有經過任何函數調用,作為對比,我們看看第一個例子的匯編代碼
main: .LFB1446: pushl %ebp .LCFI4: movl %esp, %ebp .LCFI5: subl $8, %esp .LCFI6: andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp subl $12, %esp leal -5(%ebp), %eax pushl %eax .LCFI7: call _ZN1BILi5EE3numEv 注釋1 addl $16, %esp 注釋2 movl %eax, -4(%ebp) 注釋3 subl $8, %esp pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ subl $12, %esp pushl -4(%ebp)
注釋1,注釋2,注釋3即為int i = B<5>().num();的匯編代碼,我們發現它首先進行的是一個函數調用,之後執行一個加操作,把結果放到%eax寄存器中,然後賦值給變量i,正如注釋3所示。本來在上述代碼中,如果我們這樣聲明
int a[B<5>().num()];
欲在棧上申請B<5>().num()大小空間,如果B<5>().num()是常量我們可以這樣使用,為變量的話我們需要
int *a = new int[B<5>().num()];
而當我編寫
int a[B<5>().num()];
時,我在g++ 3.4(Linux)和g++2.9(FreeBSD)下編譯居然都沒有錯誤,看來g++編譯器對此作了擴展,聲明數組的方法,在Unix係統不是正確驗證編譯常量的方法,最好方式還是看匯編代碼。
看來我剛開始要設計的兩種方案
方案1
template class S{ public: enum {value=1+S
::value} }; template<> class S{ public: enum {value=1}; };
++>方案2template class S{ public: enum {value=1+S<*((const char *)&p+1)>::value} }; template<> class S<'/0'>{ public: enum {value=1}; };
都失敗了,在方案1中模板類型不能是const char*,模板類型隻能為類類型,或者類整形類型,比如bool,int,unsigned int,double,short等,方案2中&和*不能用在常量表達式上。
嚐試使用常量方式解決編譯期間統計字符串長度的方法到此為止了,看來要和類類型結合使用,才有可能解決這個問題,未完待續。
最後更新:2017-04-02 00:06:30