初探模板元編程
關於模板元編程的知識也有所了解,相關的書籍也看過幾本,但是至今還沒有親手寫過一個模板元程序,原因就是沒有一個合適的機會應用模板元編程技術,今天在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