模板類

模板是根據參數類型生成函式和類的機制(有時稱為“參數決定類型”),通過使用模板,可以只設計一個類來處理多種類型的數據,而不必為每一種類型分別創建類。

簡介

模板是根據參數類型生成函式和類的機制(有時稱為“參數決定類型”),通過使用模板,可以只設計一個類來處理多種類型的數據,而不必為每一種類型分別創建類。

優勢

創建一個類型安全函式來返回兩個參數中較小的一個,如果不使用Templates,必須要編寫一系列如下的函式:

// min for ints

int min( int a, int b )

return ( a < b ) ? a : b;

// min for longs

long min( long a, long b )

return ( a < b ) ? a : b;

// min for chars

char min( char a, char b )

return ( a < b ) ? a : b;

//etc...

使用templates,可以減少重複部分,形成一個函式:

template <class type>

type min( type a, type b )

return ( a < b ) ? a : b;

模板能夠減少原始碼量並提高代碼的機動性而不會降低類型安全。

何時使用模板

模板經常被用來實現如下功能:

>> 創建一個類型安全的集合類(例如,堆疊)用來處理各種類型的數據

>> 為函式添加額外的類型檢查以避免獲得空指針

>> 合併操作符重載組來修改類型行為(例如智慧型指針smart pointer)

大多數以上套用可以不用模板實現;但是,模板具有以下幾個優勢:

>> 開發容易。你可以只為你的類或函式創建一個普通的版本代替手工創建特殊情況處理。

>> 理解容易。模板為抽象類型信息提供了一個直截了當的方法。

>> 類型安全。模板使用的類型在編譯時是明確的,編譯器可以在發生錯誤之前進行類型檢查。

函式模板

函式模板(function templates)

使用函式模板,你可以指定一組基於相同代碼但是處理不同類型或類的函式,例如:

template <class type> void MySwap( type& a, type& b )

{

type c( a );

a = b; b = c;

}

這段代碼定義了一個函式家族來交換函式的參數值。從這個template你可以產生一系列函式,不僅可以交換整型、長整型,而且可以交換用戶定義類型,如果類的構造函式和賦值操作符被適當地定義,MySwap函式甚至可以交換類。

另外,函式模板可以阻止你交換不同類型的對象,因為編譯器在編譯時知道參數a和b的類型。

你可以像調用一個普通函式一樣調用一個函式模板函式;不需要特殊的語法。例如:

int i, j;

char k;

MySwap( i, j ); //OK

MySwap( i, k ); //Error, different types.

可以對函式模板的template參數作外部說明,例如:

template<class T> void f(T) {...}

void g(char j)

{ f<int>(j);

//generate the specialization f(int)

}

當template參數在外部說明時,普通固定的類型轉換會轉換函式的參數為相應的函式模板參數。在上面的的例子中,編譯器會將(char j)轉換成整型

類模板

類模板(class templates)可以使用類模板創建對一個類型進行操作的類家族。

template <class T, int i> // template<模板參數表>

class TempClass //class 類名TempClass

{public:

TempClass(void);

~TempClass( void );

int MemberSet( T a, int b );

private:

T Tarray;

int arraysize;

};

在這個例子中,模板類使用了兩個參數,一個數據類型參數T和一個整型i,T參數可以傳遞一個類型,包括結構和類,i參數必須傳遞一個整數,因為I在編譯時是一個常數,你可以使用一個標準數組聲明來定義一個長度為i的成員數組模板與宏的比較(Templates vs. Macros)在很多方面,模板類似預處理宏,用給定的類型代替模板的變數。然而,模板和宏有很大的區別:

宏:

#define min(i, j) (((i) < (j)) ? (i) : (j))

模板:

template<class T>

T min (T i, T j)

{ return ((i < j) ? i : j) }

使用宏會帶來如下問題:

>> 編譯器沒有辦法檢查宏的參數的類型是否一致。宏的定義中缺少特定類型的檢查。

>> 參數i和j被被調用了2次。例如,如果任一個參數有增量,增量會被加兩次。

>> 因為宏被預處理程式編譯,編譯器錯誤信息會指向編譯處的宏,而不是宏定義本身。而且,在編譯階段宏會在編譯表中顯露出來。

模板和空指針的比較(Templates VS. Void Pointers)

現在很多用空指針實現的函式可以用模板來實現。空指針經常被用來允許函式處理未知類型的數據。當使用空指針時,編譯器不能區分類型,所以不能處理類型檢查或類型行為如使用該類型的操作符、操作符重載或構造和析構。

使用模板,你可以創建處理特定類型的數據的函式和類。類型在模板定義里看起來是抽象的。但是,在編譯時間編譯器為每一個指定的類型創建了這個函式的一個單獨版本。這使得編譯器可以使用類和函式如同他們使用的是指定的類型。模板也可以使代碼更簡潔,因為你不必為符合類型如結構類型創建特殊的程式。

模板和集合類(Templates and Collection Classes)

模板是實現集合類的一個好方法。第四版及更高版本的Microsoft Foundation Class Library使用模板實現了六個集合類:CArray, CMap, CList, CTypedPtrArray, CtypedPtrList和 CtypedPtrMap。

MyStack集合類是一個簡單的堆疊的實現。這裡有兩個模板參數,T和i,指定堆疊中的元素類型和堆疊中項數的最大值。push 和 pop成員函式添加和刪除堆疊中的項,並在堆疊底部增加。

template <class T, int i> class MyStack

{

T StackBuffer[i];

int cItems;

public:

void MyStack( void ) : cItems( i ) {};

void push( const T item ); T pop( void );

};

template <class T, int i> void MyStack< T, i >::push( const T item )

{

if( cItems > 0 ) StackBuffer[--cItems] = item;

else throw "Stack overflow error.";

return;

}

template <class T, int i> T MyStack< T, i >::pop( void )

{

if( cItems < i )

return StackBuffer[cItems++];

else throw "Stack underflow error.";

}

模板和智慧型指針(Templates and Smart Pointers)

C++允許你創建“智慧型指針”(“smart pointer”)類囊括指針和重載指針操作符來為指針操作增加新的功能。模板允許你創建普通包裝來囊括幾乎所有類型的指針。

如下的代碼概括了一個簡單的計數垃圾收集者參考。模板類Ptr<T>為任何從RefCount繼承的類實現了一個垃圾收集指針。

#include <stdio.h>

#define TRACE printf

class RefCount

{

int crefs;

public:

RefCount(void) { crefs = 0; }

~RefCount() { TRACE("goodbye(%d)\n", crefs);}

void upcount(void)

{

++crefs;

TRACE("up to %d\n", crefs);

}

void downcount(void)

{

if (--crefs == 0) { delete this; }

else TRACE("downto %d\n", crefs);

}

};

class Sample : public RefCount

{

public:

void doSomething(void) { TRACE("Did something\n");}

};

template <class T> class Ptr

{

T* p;public: Ptr(T* p_) : p(p_) { p->upcount(); }

~Ptr(void) { p->downcount(); }

operator T*(void) { return p; }

T& operator*(void) { return *p; }

T* operator->(void) { return p; }

Ptr& operator=(Ptr<T> &p_) {return operator=((T *) p_);}

Ptr& operator=(T* p_) { p->downcount(); p = p_; p->upcount(); return *this; }

};

int main()

{

Ptr<Sample> p = new Sample;

// sample #1 Ptr<Sample> p2 = new Sample;

// sample #2 p = p2;

// #1 will have 0 crefs, so it is destroyed;

// #2 will have 2 crefs. p->doSomething();

return 0;

// As p2 and p go out of scope, their destructors call

// downcount. The cref variable of #2 goes to 0, so #2 is

// destroyed

}

類RefCount 和 Ptr<T>共同為任何一個從RefCount繼承的能夠提供整數的類的每一個實例提供了一個簡單的垃圾收集解決方案。注意使用一個參數類如Ptr<T>代替多個一般類如Ptr的主要好處在於這種形式是完全的類型安全的。前面的代碼保證Ptr<T>可以被用在幾乎任何T* 使用的地方;相反,一個普通類Ptr只能提供到void*固有的轉換。

例如,考慮一個用來創建和處理檔案垃圾收集的類,符號、字元串等等。根據類模板Ptr<T>,編譯器可以創建模板類Ptr<File>,Ptr<Symbol>, Ptr<String>等等,和它們的成員函式:Ptr<File>::~Ptr(), Ptr<File>::operator File*(), Ptr<String>::~Ptr(), Ptr<String>::operator String*()等等。

相關介紹

大部分資料建議模板類的聲明和實現都在.h檔案中,這樣能夠保證正確連結。其實模板類也能夠完成聲明和實現分離,只是需要額外做一些處理。下面仍然以Stack為例:

//.h

template <class T, int i> class MyStack

{

T StackBuffer;

int cItems;

public:

void MyStack( void ) : cItems( i ) {};

void push( const T item ); T pop( void );

};

//.cpp

template <class T, int i> void MyStack< T, i >::push( const T item )

{...}

template <class T, int i> T MyStack< T, i >::pop( void )

{...}

//在.cpp檔案的最後,增加模板聲明,把有可能用到的參數組合都進行聲明即可

template class MyStack<unsigned int, 100>;

template class MyStack<float, 100>;

...

注意:這樣做的缺點是,由於進行了聲明,會把每一個參數組合都編譯,生成的檔案會非常大。

相關詞條

相關搜尋

熱門詞條

聯絡我們