一、什么是模板
模板是根據參數類型生成函數和類的機制(有時稱為“參數決定類型”)。通過使用模板,可以只設計一個類來處理多種類型的數據,而不必為每一種類型分別創建類。
例如,創建一個類型安全函數來返回兩個參數中較小的一個,如果不使用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 T> T min( T a, T b )
return ( a < b ) ? a : b;
模板能夠減少源代碼量并提高代碼的機動性而不會降低類型安全。
二、何時使用模板
模板經常被用來實現如下功能:
1、創建一個類型安全的集合類(例如,堆棧)用來處理各種類型的數據
2、為函數添加額外的類型檢查以避免獲得空指針
3、合并操作符重載組來修改類型行為(例如智能指針smart pointer)
大多數以上應用可以不用模板實現;但是,模板具有以下幾個優勢:
1、開發容易。你可以只為你的類或函數創建一個普通的版本代替手工創建特殊情況處理。
2、理解容易。模板為抽象類型信息提供了一個直截了當的方法。
3、類型安全。模板使用的類型在編譯時是明確的,編譯器可以在發生錯誤之前進行類型檢查。
三、函數模板(function templates)
使用函數模板,你可以指定一組基于相同代碼但是處理不同類型或類的函數,例如:
template <class T> void MySwap( 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<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> 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<class T> T min (T i, T j) { return ((i < j) ? i : j) }
使用宏會帶來如下問題:
1、編譯器沒有辦法檢查宏的參數的類型是否一致。宏的定義中缺少特定類型的檢查。
2、參數i和j被被調用了2次。例如,如果任一個參數有增量,增量會被加兩次。
3、因為宏被預處理程序編譯,編譯器錯誤信息會指向編譯處的宏,而不是宏定義本身。而且,在編譯階段宏會在編譯表中顯露出來。
六、模板和空指針的比較(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*()等等。
PS:另外轉載csdn某大牛的解釋:
類屬性用于實現參數化模塊,即,給程序模塊加上類型參數,使其能對不同類型的數據實施相同的操作,它是多態的一種形式。
在C++中,類屬性主要體現在類屬函數和類屬類中。
一、函數模板
例:排序用函數模板來實現。
template <class T>
void sort(T elements[], unsigned int count)
{ //取第i個元素
elements [i]
//比較第i個和第j個元素的大小
elements [i] < elements [j]
//交換第i個和第j個元素
T temp=elements [i];
elements [i] = elements [j];
elements [j] = temp;
}
......
int a[100];
sort(a,100);
double b[200];
sort(b,200);
A c[300]; //類A中需重載操作符:<和=,給出拷貝構造函數
sort(c,300);
函數模板定義了一類重載的函數,使用函數模板所定義的函數(模板函數)時,編譯系統會自動把函數模板實例化。
模板的參數可以有多個,用逗號分隔它們,如:
template <class T1, class T2>
void f(T1 a, T2 b)
{ ......
}
模板也可以帶普通參數,它們須放在類型參數的后面,調用時需顯式實例化,如:
template <class T, int size>
void f(T a)
{ T temp[size];
......
}
void main()
{ f<int,10>(1);
}
有時,需要把函數模板與函數重載結合起來用,例如:
template <class T>
T max(T a, T b)
{ return a>b?a:b;
}
…
int x,y,z;
double l,m,n;
z = max(x,y);
l = max(m,n);
問題:max(x,m)如何處理?
定義一個max的重載函數:
double max(int a,double b)
{ return a>b?a:b;
}
二、類屬類
類定義帶有類型參數,一般用類模板實現。
例:定義一個棧類,其元素類型可以變化。
template <class T>
class Stack
{ T buffer[100];
public:
void push(T x);
T pop();
};
template <class T>
void Stack <T>::push(T x) { … }
template <class T>
T Stack <T>::pop() { … }
……
Stack <int> st1;
Stack <double> st2;
類模板定義了若干個類,類模板的實例化是顯式的。
類模板的參數可以有多個,其中包括普通參數,用逗號分隔它們。并且普通參數須放在類型參數的后面。
例:定義不同大小的棧模板
template <class T, int size>
class Stack
{ T buffer[size];
public:
void push(T x);
T pop();
};
template <class T,int size>
void Stack <T,size>::push(T x) { … }
template <class T, int size>
T Stack <T,size>::pop() { … }
……
Stack <int,100> st1;
Stack <double,200> st2;
注意:
1)類模板不能嵌套(局部類模板)。
2)類模板中的靜態成員僅屬于實例化后的類(模板類),不同實例之間不存在共享。
3)模板也是一種代碼復用機制 。
模板提供了代碼復用。在使用模板時首先要實例化,即生成一個具體的函數或類。函數模板的實例化是隱式實現的,即由編譯系統根據對具體模板函數(實例化后的函數)的調用來進行相應的實例化,而類模板的實例化是顯式進行的,在創建對象時由程序指定。
一個模板有很多實例,是否實例化模板的某個實例由使用點來決定,如果未使用到一個模板的某個實例,則編譯系統不會生成相應實例的代碼。
在C++中,由于模塊是分別編譯的,如果在模塊A中要使用模塊B中定義的一個模板的某個實例,而在模塊B中未使用這個實例,則模塊A無法使用這個實例,除非在模塊A中也定義了相應的模板。因此模板是基于源代碼復用,而不是目標代碼復用。
例:
// file1.h
template <class T>
class S
{ T a;
public:
void f();
};
// file1.cpp
#include "file1.h"
template <class T>
void S<T>::f()
{ …
}
template <class T>
T max(T x, T y)
{ return x>y?x:y;
}
void main()
{ int a,b;
float m,n;
max(a,b);
max(m,n);
S<int> x;
x.f();
}
// file2.cpp
#include "file1.h"
extern double max(double,double);
void sub()
{ max(1.1,2.2); //Error,no appropriate instance
S<float> x;
x.f(); //Error, corresponding instance has no appropriate implementation
}
再PS:轉載cppblog上某位大牛的理解:
a. 能不用高級特性就不用高級的,其實無所謂過程式編程與OOP和范型編程的高低之分,好比是數學中的加減乘除。盡可能抑制使用template的沖動
b. 象Loki中的Policy-Based design是非常高級,以至于出現template<template<...> class X, ...>的code,導致編譯時過長,由于模板天生就是內聯語義,一個優化的compiler即使在開啟mix size選項時,還是有太多的展開代碼,所以編譯時長長,程序大大。還是在確實存在設計時就可預見的概念互補,可替換時使用template帶Policy-Based design。
c. 對于Template Meta Programming和Loki中使用TypeList做編譯時產生繼承體系的方法,個人覺得用于程序優化很好,其實它們可以用不少現存的替換方案解決的,不過要學習些新東西,比如:ML,一些與c++內嵌腳本語言,還有專門的指令優化(MMX,SSE),還可以自制一個代碼生成器,或是一些預計算技術。
d. 一個不成熟的觀點,當用template時想想真的需要這么做嗎?