在很多方面,模板类似预处理宏,用给定的类型代替模板的变量。然而,模板和宏有很大的区别:
宏:
#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*()等等。