閱讀345 返回首頁    go 阿裏雲 go 技術社區[雲棲]


初探模板元編程

關於模板元編程的知識也有所了解,相關的書籍也看過幾本,但是至今還沒有親手寫過一個模板元程序,原因就是沒有一個合適的機會應用模板元編程技術,今天在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
運行期
對象使用
函數調用
變量賦值
操作變量時&,+=,++,--等運算符。

所以,如果想實現模板元編程,必須要把握的是一定要在編譯期完成程序,而不是在程序的運行期,仔細區分運行期和編譯期是模板元編程的第一步。

那麼根據以上要求,一種比較正確的方法是,在下麵的例子中融合了enumconst 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>::valueB<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:

上述代碼中注釋1int i = B<5>::value;的匯編代碼,注釋2int 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.4Linux)和g++2.9FreeBSD)下編譯居然都沒有錯誤,看來g++編譯器對此作了擴展,聲明數組的方法,在Unix係統不是正確驗證編譯常量的方法,最好方式還是看匯編代碼。


看來我剛開始要設計的兩種方案

方案1

template
class S{
    public:
        enum {value=1+S

::value} }; template<> class S{ public: enum {value=1}; };

++>
方案2
template
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

  上一篇:go 利用Socket提交文件到web server
  下一篇:go Spring Framework 2.5 Reference中文版正式發布