一、什么是模板
模板是根據(jù)參數(shù)類型生成函數(shù)和類的機制(有時稱為“參數(shù)決定類型”)。通過使用模板,可以只設(shè)計一個類來處理多種類型的數(shù)據(jù),而不必為每一種類型分別創(chuàng)建類。
例如,創(chuàng)建一個類型安全函數(shù)來返回兩個參數(shù)中較小的一個,如果不使用Templates,必須要編寫一系列如下的函數(shù):
// 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,可以減少重復(fù)部分,形成一個函數(shù):
template <class T> T min( T a, T b )
return ( a < b ) ? a : b;
模板能夠減少源代碼量并提高代碼的機動性而不會降低類型安全。
二、何時使用模板
模板經(jīng)常被用來實現(xiàn)如下功能:
1、創(chuàng)建一個類型安全的集合類(例如,堆棧)用來處理各種類型的數(shù)據(jù)
2、為函數(shù)添加額外的類型檢查以避免獲得空指針
3、合并操作符重載組來修改類型行為(例如智能指針smart pointer)
大多數(shù)以上應(yīng)用可以不用模板實現(xiàn);但是,模板具有以下幾個優(yōu)勢:
1、開發(fā)容易。你可以只為你的類或函數(shù)創(chuàng)建一個普通的版本代替手工創(chuàng)建特殊情況處理。
2、理解容易。模板為抽象類型信息提供了一個直截了當(dāng)?shù)姆椒ā?br>3、類型安全。模板使用的類型在編譯時是明確的,編譯器可以在發(fā)生錯誤之前進行類型檢查。
三、函數(shù)模板(function templates)
使用函數(shù)模板,你可以指定一組基于相同代碼但是處理不同類型或類的函數(shù),例如:
template <class T> void MySwap( T& a, T& b )
{
T c( a );
a = b; b = c;}
這段代碼定義了一個函數(shù)家族來交換函數(shù)的參數(shù)值。從這個template你可以產(chǎn)生一系列函數(shù),不僅可以交換整型、長整型,而且可以交換用戶定義類型,如果類的構(gòu)造函數(shù)和賦值操作符被適當(dāng)?shù)囟x,MySwap函數(shù)甚至可以交換類。
另外,函數(shù)模板可以阻止你交換不同類型的對象,因為編譯器在編譯時知道參數(shù)a和b的類型。你可以像調(diào)用一個普通函數(shù)一樣調(diào)用一個函數(shù)模板函數(shù);不需要特殊的語法。例如:
int i, j;
char k;
MySwap( i, j ); //OK
MySwap( i, k ); //Error, different types.
可以對函數(shù)模板的template參數(shù)作外部說明,例如:
template<class T> void f(T) {...}
void g(char j) {
f<int>(j); //generate the specialization f(int)
}
當(dāng)template參數(shù)在外部說明時,普通固定的類型轉(zhuǎn)換會轉(zhuǎn)換函數(shù)的參數(shù)為相應(yīng)的函數(shù)模板參數(shù)。在上面的的例子中,編譯器會將(char j)轉(zhuǎn)換成整型。
四、類模板(class templates)
可以使用類模板創(chuàng)建對一個類型進行操作的類家族。
template <class T, int i> class TempClass
{
public:
TempClass( void );
~TempClass( void );
int MemberSet( T a, int b );
private:
T Tarray[i];
int arraysize;
};
在這個例子中,模板類使用了兩個參數(shù),一個類型T和一個整數(shù)i,T參數(shù)可以傳遞一個類型,包括結(jié)構(gòu)和類,i參數(shù)必須傳第一個整數(shù),因為I在編譯時是一個常數(shù),你可以使用一個標(biāo)準(zhǔn)數(shù)組聲明來定義一個長度為i的成員數(shù)組。
五、模板與宏的比較(Templates vs. Macros)
在很多方面,模板類似預(yù)處理宏,用給定的類型代替模板的變量。然而,模板和宏有很大的區(qū)別:
宏:
#define min(i, j) (((i) < (j)) ? (i) : (j))
模板:
template<class T> T min (T i, T j) { return ((i < j) ? i : j) }
使用宏會帶來如下問題:
1、編譯器沒有辦法檢查宏的參數(shù)的類型是否一致。宏的定義中缺少特定類型的檢查。
2、參數(shù)i和j被被調(diào)用了2次。例如,如果任一個參數(shù)有增量,增量會被加兩次。
3、因為宏被預(yù)處理程序編譯,編譯器錯誤信息會指向編譯處的宏,而不是宏定義本身。而且,在編譯階段宏會在編譯表中顯露出來。
六、模板和空指針的比較(Templates VS. Void Pointers)
現(xiàn)在很多用空指針實現(xiàn)的函數(shù)可以用模板來實現(xiàn)。空指針經(jīng)常被用來允許函數(shù)處理未知類型的數(shù)據(jù)。當(dāng)使用空指針時,編譯器不能區(qū)分類型,所以不能處理類型檢查或類型行為如使用該類型的操作符、操作符重載或構(gòu)造和析構(gòu)。
使用模板,你可以創(chuàng)建處理特定類型的數(shù)據(jù)的函數(shù)和類。類型在模板定義里看起來是抽象的。但是,在編譯時間編譯器為每一個指定的類型創(chuàng)建了這個函數(shù)的一個單獨版本。這使得編譯器可以使用類和函數(shù)如同他們使用的是指定的類型。模板也可以使代碼更簡潔,因為你不必為符合類型如結(jié)構(gòu)類型創(chuàng)建特殊的程序。
七、模板和集合類(Templates and Collection Classes)
模板是實現(xiàn)集合類的一個好方法。第四版及更高版本的Microsoft Foundation Class Library使用模板實現(xiàn)了六個集合類:CArray, CMap, CList, CTypedPtrArray, CtypedPtrList和 CtypedPtrMap。
MyStack集合類是一個簡單的堆棧的實現(xiàn)。這里有兩個模板參數(shù),T和i,指定堆棧中的元素類型和堆棧中項數(shù)的最大值。push 和 pop成員函數(shù)添加和刪除堆棧中的項,并在堆棧底部增加。
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++允許你創(chuàng)建“智能指針”(“smart pointer”)類囊括指針和重載指針操作符來為指針操作增加新的功能。模板允許你創(chuàng)建普通包裝來囊括幾乎所有類型的指針。
如下的代碼概括了一個簡單的計數(shù)垃圾收集者參考。模板類Ptr<T>為任何從RefCount繼承的類實現(xiàn)了一個垃圾收集指針。
#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繼承的能夠提供整數(shù)的類的每一個實例提供了一個簡單的垃圾收集解決方案。注意使用一個參數(shù)類如Ptr<T>代替多個一般類如Ptr的主要好處在于這種形式是完全的類型安全的。前面的代碼保證Ptr<T>可以被用在幾乎任何T* 使用的地方;相反,一個普通類Ptr只能提供到void*固有的轉(zhuǎn)換。
例如,考慮一個用來創(chuàng)建和處理文件垃圾收集的類,符號、字符串等等。根據(jù)類模板Ptr<T>,編譯器可以創(chuàng)建模板類Ptr<File>,Ptr<Symbol>, Ptr<String>等等,和它們的成員函數(shù):Ptr<File>::~Ptr(), Ptr<File>::operator File*(), Ptr<String>::~Ptr(), Ptr<String>::operator String*()等等。
PS:另外轉(zhuǎn)載csdn某大牛的解釋:
類屬性用于實現(xiàn)參數(shù)化模塊,即,給程序模塊加上類型參數(shù),使其能對不同類型的數(shù)據(jù)實施相同的操作,它是多態(tài)的一種形式。
在C++中,類屬性主要體現(xiàn)在類屬函數(shù)和類屬類中。
一、函數(shù)模板
例:排序用函數(shù)模板來實現(xiàn)。
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中需重載操作符:<和=,給出拷貝構(gòu)造函數(shù)
sort(c,300);
函數(shù)模板定義了一類重載的函數(shù),使用函數(shù)模板所定義的函數(shù)(模板函數(shù))時,編譯系統(tǒng)會自動把函數(shù)模板實例化。
模板的參數(shù)可以有多個,用逗號分隔它們,如:
template <class T1, class T2>
void f(T1 a, T2 b)
{ ......
}
模板也可以帶普通參數(shù),它們須放在類型參數(shù)的后面,調(diào)用時需顯式實例化,如:
template <class T, int size>
void f(T a)
{ T temp[size];
......
}
void main()
{ f<int,10>(1);
}
有時,需要把函數(shù)模板與函數(shù)重載結(jié)合起來用,例如:
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的重載函數(shù):
double max(int a,double b)
{ return a>b?a:b;
}
二、類屬類
類定義帶有類型參數(shù),一般用類模板實現(xiàn)。
例:定義一個棧類,其元素類型可以變化。
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;
類模板定義了若干個類,類模板的實例化是顯式的。
類模板的參數(shù)可以有多個,其中包括普通參數(shù),用逗號分隔它們。并且普通參數(shù)須放在類型參數(shù)的后面。
例:定義不同大小的棧模板
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)類模板中的靜態(tài)成員僅屬于實例化后的類(模板類),不同實例之間不存在共享。
3)模板也是一種代碼復(fù)用機制 。
模板提供了代碼復(fù)用。在使用模板時首先要實例化,即生成一個具體的函數(shù)或類。函數(shù)模板的實例化是隱式實現(xiàn)的,即由編譯系統(tǒng)根據(jù)對具體模板函數(shù)(實例化后的函數(shù))的調(diào)用來進行相應(yīng)的實例化,而類模板的實例化是顯式進行的,在創(chuàng)建對象時由程序指定。
一個模板有很多實例,是否實例化模板的某個實例由使用點來決定,如果未使用到一個模板的某個實例,則編譯系統(tǒng)不會生成相應(yīng)實例的代碼。
在C++中,由于模塊是分別編譯的,如果在模塊A中要使用模塊B中定義的一個模板的某個實例,而在模塊B中未使用這個實例,則模塊A無法使用這個實例,除非在模塊A中也定義了相應(yīng)的模板。因此模板是基于源代碼復(fù)用,而不是目標(biāo)代碼復(fù)用。
例:
// 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:轉(zhuǎn)載cppblog上某位大牛的理解:
a. 能不用高級特性就不用高級的,其實無所謂過程式編程與OOP和范型編程的高低之分,好比是數(shù)學(xué)中的加減乘除。盡可能抑制使用template的沖動
b. 象Loki中的Policy-Based design是非常高級,以至于出現(xiàn)template<template<...> class X, ...>的code,導(dǎo)致編譯時過長,由于模板天生就是內(nèi)聯(lián)語義,一個優(yōu)化的compiler即使在開啟mix size選項時,還是有太多的展開代碼,所以編譯時長長,程序大大。還是在確實存在設(shè)計時就可預(yù)見的概念互補,可替換時使用template帶Policy-Based design。
c. 對于Template Meta Programming和Loki中使用TypeList做編譯時產(chǎn)生繼承體系的方法,個人覺得用于程序優(yōu)化很好,其實它們可以用不少現(xiàn)存的替換方案解決的,不過要學(xué)習(xí)些新東西,比如:ML,一些與c++內(nèi)嵌腳本語言,還有專門的指令優(yōu)化(MMX,SSE),還可以自制一個代碼生成器,或是一些預(yù)計算技術(shù)。
d. 一個不成熟的觀點,當(dāng)用template時想想真的需要這么做嗎?