泛型编程与面向对象编程一样,都依赖于某种形式的多态性。面向对象编程中的多态性在运行时应用于存在继承关系的类。我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异。只要使用基类的引用或指针,基类类型或派生类类型的对象就可以使用相同的代码。
在泛型编程中,我们所编写的类和函数能够多态地用于跨越编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。标准库中的容器、迭代器和算法是很好的泛型编程的例子。
在 C++ 中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。
5.1 模板的定义和使用
5.1.1 定义函数模板
我们可以不用为每个类型定义一个新函数,而是只定义一个函数模板(function template)。函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。例如,可以编写名为 compare 的函数模板,它告诉编译器如何为我们想要比较的类型产生特定的 compare 版本。下面是 compare 的模板版本:
// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。 函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。类型形参跟在关键字 class 或 typename 之后定义,例如,class T 是名为 T 的类型形参,在这里 class 和 typename 没有区别。
使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。例如:
int main ()
{
// T is int;
// compiler instantiates int compare(const int&, const int&)
cout << compare(1, 0) << endl;
// T is string;
// compiler instantiates int compare(const string&, const string&)
string s1 = "hi", s2 = "world";
cout << compare(s1, s2) << endl;
return 0;
}
// ok: inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);
5.1.2 类模板
我们这里以STL库中的queue类的实现为例来介绍类模板的使用,我们自定义的 Queue 类必须能够支持不同类型的对象,所以将它定义为类模板。
先定义这个类的接口:
template <class Type> class Queue {
// needs access to head
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
public:
// empty Queue
Queue(): head(0), tail(0) { }
public:
// construct a Queue from a pair of iterators on some sequence
template <class It>
Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// . . .
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&);
~Queue() { destroy(); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// rest of Queue class as before
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
void push(const Type &); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const { // true if no elements in the Queue
return head == 0;
}
private:
QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue
// utility functions used by copy constructor, assignment, and destructor
void destroy(); // delete all the elements
void copy_elems(const Queue&); // copy elements from parameter
private:
// version of copy to be used by assign to copy elements from iterator range
template <class Iter> void copy_elems(Iter, Iter);
};
除了模板形参表外,类模板的定义看起来与任意其他类问相似。类模板可以定义数据成员、函数成员和类型成员,也可以使用访问标号控制对成员的访问,还可以定义构造函数和析构函数等等。在类和类成员的定义中,可以使用模板形参作为类型或值的占位符,在使用类时再提供那些类型或值。与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:
Queue<int> qi; // Queue that holds ints Queue< vector<double> > qc; // Queue that holds vectors of doubles Queue<string> qs; // Queue that holds strings
5.1.3 模板形参
typedef double T;
template <class T> T calc(const T &a, const T &b)
{
// tmp has the type of the template parameter T
// not that of the global typedef
T tmp = a;
// ...
return tmp;
}
而且,用作模板的名字不能在模板的内部重用,而且形参的名字在模板形参表中只能使用一次。 // all three uses of calc refer to the same function template
// forward declarations of the template
template <class T> T calc(const T&, const T&) ;
template <class U> U calc(const U&, const U&) ;
// actual definition of the template
template <class Type>
Type calc(const Type& a, const Type& b) { /* ... */ }
5.1.4 类型模板形参
template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
Parm::size_type * p; // If Parm::size_type is a type, then a declaration
// If Parm::size_type is an object, then multiplication
}
template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
typename Parm::size_type * p; // ok: declares p to be a pointer
}
通过在成员名前加上关键字 typename 作为前缀,可以告诉编译器将成员当作类型。通过编写 typename parm::size_type,指出绑定到 Parm 的类型的 size_type 成员是类型的名字。当然,这一声明给用实例化 fcn 的类型增加了一个职责:那些类型必须具有名为 size_type 的成员,而且该成员是一个类型。5.1.4 非类型模板形参
模板形参不必都是类型。在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。 // initialize elements of an array to zero
template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度。当调用 array_init 时,编译器从数组实参计算非类型形参的值: int x[42];
double y[10];
array_init(x); // instantiates array_init(int(&)[42]
array_init(y); // instantiates array_init(double(&)[10]
5.1.5 模板代码的编写规范
- 模板的形参是 const 引用(因为有些类型是不允许复制的,这种用法可以防止这种风险)。
- 函数体中的测试只用 < 比较。