模板
模板是C++中泛型编程的基础。一个模板就是一个创建类或函数的蓝图或公式,在使用模板时,我们提供足够的信息将模板转换为特定的类或函数。这种转换发生在编译时。
函数模板
考虑这样一个功能的实现:交换两个静态类型相同的变量的值,编写一个swap函数。
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
上面的swap函数只能接受两个整型实参并交换它们,如果要实现更多类型变量的值交换,我们要写出很多个重载函数:swap(double &a, double &b) swap(char &a, char &b)……
在这里运用函数重载显然效率低下,我们希望定义一个通用的swap函数,能够接受多种类型的实参。用函数模板可以做到这一点:
template <typename T>
void swap(T &t1, T &t2) {
T temp = t1;
t1 = t2;
t2 = temp;
}
如上,函数模板就是一个公式,可用来生成针对特定类型的函数版本。编译器(通常)根据函数实参的类型来为我们推断模板实参,并生成相对应类型的swap版本。这极大减少了代码编写的工作量,提高了开发效率。
非类型模板参数
上面的swap模板中,T属于模板类型参数。一般来说,我们可以将类型参数看作类型说明符,就像内置类型或类类型来使用:指定返回类型或函数的参数类型,以及函数体内的变量声明或类型转换等。
除了定义类型参数,还可以在模板中定义非类型参数(nontype parameter)。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字class或typename来指定非类型参数。
例如,编写一个处理字符串字面常量的大小比较函数。
template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcmp(p1, p2);
}
当我们调用compare时:compare("hi", "mom") 编译器会使用字面常量的大小来代替N和M,从而实例化模板。因此编译器会实例化出如下版本:
int compare(const char (&p1)[3], const char (&p2)[4])//在字面值常量末尾自动添加空字符'\0'
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针(或左值)引用。绑定到非类型参数的模板实参必须是常量表达式。绑定到指针或引用非类型参数的实参还必须具有静态的生存期。我们不能用一个非static局部变量或动态对象作为指针或引用非类型模板参数的实参,否则编译器无法进行参数推导。
类模板
类模板用来生成类的蓝图。与函数模板的不同之处在于,编译器不能为类模板腿短模板参数类型,我们必须在使用类模板时在模板名后的尖括号中提供额外信息——用来代替模板参数的模板实参列表。
template <typename T>
class A {
public:
A(a) : mem(a) {}
void print() { std::cout << this->mem << std::endl; }
private:
T mem;
}
实例化类模板时,必须提供额外信息,如:A<int> A_class 将会生成一个A<int>类型的对象。一个类模板的每个实例都形成一个独立的类,如A<int>和A<double>是两个独立的类。
类模板与友元
当一个类包含一个友元声明时,类与友元各自是否是模板时相互无关的。如果一个类模板包含一个非模板友元,则友元可以被授权访问所有模板的实例,也可以被授权只能访问指定类型的模板实例。如果友元自身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例。从C++11标准尅是,我们甚至可以将模板类型参数声明为友元。
成员模板
分为两种,一种是非模板类的成员模板,另一种是类模板的成员模板。对于后者,类和成员各自有各自的独立的模板参数,实例化时必须同时提供类和函数模板的实参。
本文部分内容摘自《C++ Primer 中文版(第五版)》