C++中的模板(templates) (轉)

worldblog發表於2007-12-10
C++中的模板(templates) (轉)[@more@] 

作者:苗新東

什麼是模板

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

  例如,建立一個型別函式來返回兩個引數中較小的一個,如果不使用Templates,必須要編寫一系列如下的函式:

// min for ints:namespace prefix = o ns = "urn:schemas--com::office" />


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 T min( T a, T b )


  return ( a < b ) ? a : b;


模板能夠減少量並提高程式碼的機動性而不會降低型別安全。

何時使用模板

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

Ø  建立一個型別安全的集合類(例如,堆疊)用來處理各種型別的資料

Ø  為函式新增額外的型別檢查以避免獲得空指標

Ø  合併運算子過載組來修改型別行為(例如智慧指標smart pointer)

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

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

Ø  理解容易。模板為抽象型別資訊提供了一個直截了當的方法。

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

函式模板(function templates)

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

  template void MyS( T& a, T& b )

{


  T 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 void f(T) {...}


void g(char j) {


  f(j);  //generate the specialization f(int)


}


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

類别範本(class templates)

可以使用類别範本建立對一個型別進行操作的類家族。

template class TempClass


{


public:


  TempClass( void );


  ~TempClass( void );


  int MemberSet( T a, int b );


private:


  T Tarray[i];


  int arraysize;


};


在這個例子中,模板類使用了兩個引數,一個型別T和一個整數i,T引數可以傳遞一個型別,包括結構和類,i引數必須傳第一個整數,因為I在編譯時是一個常數,你可以使用一個標準陣列宣告來定義一個長度為i的成員陣列

模板與宏的比較(Templates vs. Macros)

在很多方面,模板類似預處理宏,用給定的型別代替模板的變數。然而,模板和宏有很大的區別:

宏:

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


模板:

template 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 MyStack


{


  T StackBuffer[i];


  int cItems;


public:


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


  void push( const T item );


  T pop( void );


};


 


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


{


  if( cItems > 0 )


  StackBuffer[--cItems] = item;


  else


  throw "Stack overflow error.";


  return;


}


 


template 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為任何從RefCount繼承的類實現了一個垃圾收集指標。

#include


 


#define TRACE printf


 


class RefCount {


  int crefs;


public:


  RefCount(void) { crefs = 0; }


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


  void upcount(void) { ++crefs; TRACE("up to %dn", crefs);}


  void downcount(void)


  {


  if (--crefs == 0)


  {


  delete this;


  }


   else


  TRACE("downto %dn", crefs);


  }


};


 


class Sample : public RefCount {


public:


  void doSomething(void) { TRACE("Did somethingn");}


};


 


template 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 &p_)


  {return operator=((T *) p_);}


  Ptr& operator=(T* p_) {


  p->downcount(); p = p_; p->upcount(); return *this;


  }


};


 


int main() {


  Ptr p  = new Sample; // sample #1


  Ptr 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 pe, their destructors call


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


  // destroyed


}


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

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


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990988/,如需轉載,請註明出處,否則將追究法律責任。

相關文章