泛型编程
面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:OOP能处理类型在程序运行之前都未知的情况;而在泛型编程中,在编译时就能获知类型了。(这句话还得慢慢去理解)
模板是C++中泛型编程的基础。一个模板就是一个创建类或者函数的蓝图或者说公式。当使用一个vector这样的泛型类型,或者find这样的泛型函数时,我们提供足够的信息,将蓝图转换为特定的类或函数。
1 函数模板
函数模板的定义
假定我们希望编写一个函数来比较两个变量的大小,在实际中,由于变量的类型不同,我们可能需要定义多个重载函数,比如下面两种:
//比较两个变量v1,v2的大小,相等返回0,v1大返回1,v2大返回-1
int compare(const string &v1, const string &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int compare(const double &v1, const double &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
这两个函数几乎是相同的,唯一的差异是参数的类型,函数体完全一样。为了避免重复定义,我们可以定义一个通用的函数模板(function template),compare的函数模板可以定义如下:
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
函数模板需要注意一下几点:
- 在模板定义中,模板参数列表(也就是尖括号里面的部分)不能为空。
- 类型参数(也就是T)前必须使用关键字class或者typename(两者可以互换使用)。
函数模板的实例化
当我们调用一个模板函数时,编译器可以通过函数实参来为我们推断模板实参的类型。比如在下面的调用中:
cout << compare(1, 0) << endl; //T为int
实参类型为int,编译器会推断出模板实参为int,并将它绑定到模板参数T。
编译器也会用推断出的模板参数来为我们 实例化(instantiate) 一个特定版本的函数,如下:
int compare(const int &v1, const int &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
这些编译器生成的版本通常被成为模板的实例(instantiation)。
2 类模板
类模板(class template) 是用来生成类的蓝图的。与函数模板不同的是,编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在模板名之后的尖括号提供额外信息(比如vector<int>)。
类模板的定义
假定我们希望定义一个名为Blob的模板,保存一组元素。与容器不同,我们希望Blob对象的不同拷贝之间共享相同的元素。即,当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素。Blob模板定义如下:
template <typename T> class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// 构造函数
Blob();
Blob(std::initializer_list<T> il);
// Blob中的元素数目
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// 添加和删除元素
void push_back(const T &t) { data->push_back(t);}
// 移动版本
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
T& back();
T& operator[](size_type i);
private:
std::shared_ptr<std::vector<T>> data;
// 若data[i]无效,则抛出msg
void check(size_type i, const std::string &msg) const;
};
类模板的实例化
当使用一个类模板时,我们必须提供额外信息,即显式模板实参(explicit template argument) 列表,它们被绑定模板参数。编译器使用这些模板实参来实例化出特定的类。比如,下面是Blob模板实例化的一个例子:
Blob<int> ia; //空Blob<int>
Blob<int> ia2 = {0, 1, 2, 3, 4} //有5个元素的Blob<int>
3 成员模板
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板(member template)。成员模板不能是虚函数。
普通类的成员模板
class DebugDelete {
public:
DebugDelete(std::ostream &s = std::cerr) : os(s) { }
// 与任何函数模板相同,T的类型由编译器推断
template <typename T> void operator()(T *p) const { os << "deleting unique_ptr"} << std::endl; delete p; }
private:
std::ostream &os;
};
类模板的成员模板
template <typename T> class Blob {
template <typename It> Blob(It b, It e);
//...
};