一、什么是模板
模板是根据参数类型生成函数和类的机制(有时称为“参数决定类型”)。通过使用模板,可以只设计一个类来处理多种类型的数据,而不必为每一种类型分别创建类。
例如,创建一个类型安全函数来返回两个参数中较小的一个,如果不使用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时想想真的需要这么做吗?