這兩天一直被這個模板元編程給迷住了,覺得它真是一個很好的東西!於是好奇就仔細的研究了下,之前看過幾篇文章大概的意思就是“編可以編程的程式”。聽起來很神奇吧。
其基本原理也就是讓編譯器在編譯期間就計算好一些我們需要計算的值。在程式運行期間就不需要再去計算這些值了,從而提高程式的運行效能。當然這樣做會讓程式編譯起來很慢,一般不常用。不過在一些需要的地方我們還是捨得編譯的效率問題的。。
先寫個例子看看其中的原理:
#include <iostream><br />using namespace std;</p><p>template< int x, int y ><br />struct ADD<br />{<br /> enum{ Result = ( x > y ) ? x + 20 : y + 100 };<br />};</p><p>int main( void )<br />{<br /> int a = ADD< 2, 3 >::Result;</p><p> system( "pause" );<br /> return 0;<br />}
在上邊的代碼裡,我們看模板結構裡面傳了倆整形的變數。結構體裡面用來產生枚舉值,我們可以先從理論上得到結論是在編譯期間這些枚舉值就會被計算出來。是一個透明的資料。而且最終得到的a應該等於103。我們先運行:
輸出結果:103
結果沒錯,但是我們看不到到底是不是在編譯期間完成的啊。。怎麼辦呢? 於是在 int a = ADD< 2, 3 >::Result; 執行之前設個斷點,或者按F10直接斷在main函數開頭。我們來查看反組譯碼代碼:
00411B00 push ebp
00411B01 mov ebp,esp
00411B03 sub esp,0CCh
00411B09 push ebx
00411B0A push esi
00411B0B push edi
00411B0C lea edi,[ebp-0CCh]
00411B12 mov ecx,33h
00411B17 mov eax,0CCCCCCCCh
00411B1C rep stos dword ptr [edi]
00411B1E mov dword ptr [a],67h
00411B25 push offset string "pause" (4260C8h)
00411B2A call @ILT+565(_system) (41123Ah)
00411B2F add esp,4
00411B32 xor eax,eax
上邊是整個main函數的反組譯碼代碼,我們可以看到上邊紅色的那句彙編代碼就是給a 賦枚舉值Result,看直接MOV的是67h = 103! 由此可以證明我們的枚舉值是在編譯期間就計算好了的。。
前面看過一篇文章,是關於用模板元來解迴圈。我們以求一個整形數組所有元素之和為例子寫了一個測試:
#include <iostream>
using namespace std;
template< int count, class Ty >
class ADD
{
public:
static int Result( Ty* elem )
{
return elem[ 0 ] + ADD< count - 1, Ty >::Result( elem++ );
}
};
template< class Ty >
class ADD< 1, Ty >
{
public:
static int Result( Ty* elem )
{
return elem[ 0 ];
}
};
int main( void )
{
__int64 BegTime = 0;
__int64 EndTime = 0;
__int32 sum = 0;
int a[ 3 ] = { 4, 5, 6 };
__asm
{
rdtsc
mov dword ptr [ BegTime ], eax
lea eax, dword ptr [ BegTime ]
mov dword ptr [ eax + 4 ], edx
}
ADD< 3, int >::Result( a );
__asm
{
rdtsc
mov dword ptr [ EndTime ], eax
lea eax, dword ptr [ EndTime ]
mov dword ptr [ eax + 4 ], edx
}
cout << EndTime - BegTime << endl;
BegTime = 0;
EndTime = 0;
__asm
{
rdtsc
mov dword ptr [ BegTime ], eax
lea eax, dword ptr [ BegTime ]
mov dword ptr [ eax + 4 ], edx
}
for ( int i = 0; i < 3; ++i )
{
sum += a[ i ];
}
__asm
{
rdtsc
mov dword ptr [ EndTime ], eax
lea eax, dword ptr [ EndTime ]
mov dword ptr [ eax + 4 ], edx
}
cout << EndTime - BegTime << endl;
system( "pause" );
return 0;
}
上邊的程式中紅色部分表示模板元終結模板吧,我是這樣理解的。。名字無所謂啦 - - 它的意思是當我程式執行到模板參數一為1的情況寫就終結調用了。 當然可以參數2或者多個限定。過程是這樣的:
調用順序為: ADD< 3, int >::Result( int* ) => ADD< 2, int >::Result( int* ) => ADD< 1, int >::Result( int* )相當於轉換成遞迴了 。。。
那我們在編譯期間是不是編譯器就已經遞迴調用了Function Compute出結果,我們在運行時只需要取這個值這麼簡單呢?我們斷在ADD< 3, int >::Result( a ); 之前或它上邊,來看看反組譯碼代碼:
0041B291 lea eax,[a]
0041B294 push eax
0041B295 call ADD<3,int>::Result (419A82h)
0041B29A add esp,4
我們在藍色的地方F11跟進去,
一直跟到這裡:
0041BFCE mov eax,dword ptr [elem]
0041BFD1 mov dword ptr [ebp-0C4h],eax
0041BFD7 mov ecx,dword ptr [elem]
0041BFDA add ecx,4
0041BFDD mov dword ptr [elem],ecx
0041BFE0 mov edx,dword ptr [ebp-0C4h]
0041BFE6 push edx
0041BFE7 call ADD<2,int>::Result (4193CFh)
0041BFEC add esp,4
0041BFEF mov ecx,dword ptr [elem]
0041BFF2 add eax,dword ptr [ecx]
我們再在藍色的地方跟進去,
一直到這裡:
0041C6AE mov eax,dword ptr [elem]
0041C6B1 mov dword ptr [ebp-0C4h],eax
0041C6B7 mov ecx,dword ptr [elem]
0041C6BA add ecx,4
0041C6BD mov dword ptr [elem],ecx
0041C6C0 mov edx,dword ptr [ebp-0C4h]
0041C6C6 push edx
0041C6C7 call ADD<1,int>::Result (41A063h)
0041C6CC add esp,4
0041C6CF mov ecx,dword ptr [elem]
0041C6D2 add eax,dword ptr [ecx]
我們再在藍色的地方跟進去,
一直到這裡:
0041CAE0 push ebp
0041CAE1 mov ebp,esp
0041CAE3 sub esp,0C0h
0041CAE9 push ebx
0041CAEA push esi
0041CAEB push edi
0041CAEC lea edi,[ebp-0C0h]
0041CAF2 mov ecx,30h
0041CAF7 mov eax,0CCCCCCCCh
0041CAFC rep stos dword ptr [edi]
0041CAFE mov eax,dword ptr [elem]
0041CB01 mov eax,dword ptr [eax]
看到整個過程了吧,這裡可以明白一點就是在上邊的彙編代碼裡面我標註成紅色的數字是在編譯期間計算好了的。我們在定位的時候直接就傳入了模板元在編譯期間計算後的數字。但是在這裡並沒有看到整個調用過程在編譯期間執行。之前看過的文章說會在編譯期間執行也不知道為什麼。我們上邊的測試是可以在運行期間斷下來的並且執行了遞迴調用。
__asm
{
rdtsc
mov dword ptr [ BegTime ], eax
lea eax, dword ptr [ BegTime ]
mov dword ptr [ eax + 4 ], edx
}
這段代碼在前面的博文中已經提到過,是擷取程式運行以來的時間(納秒級)。
這裡測試我們模板元解開迴圈後的速度和迴圈的速度。
程式輸出結果為:
3090周期
130周期
這裡的周期除以CPU主頻率得到啟動並執行時間,這裡不用計算就能明顯區分出來了 - -
可見解開迴圈只是變成了遞迴,真正起到作用的我認為是在編譯期間計算好了每次遞迴所應該傳入的整形數值。但是遞迴開銷很大,而且很容易堆疊溢位。。所以這個問題還得我們深思。。不過在編譯期間運算一些比如第一個例子計算枚舉或類似的操作時,模板元的好處就得以體現了。。
有什麼說的不對的地方望大家指教。。我是菜鳥 - -