类模板是用于生成类的蓝图的。与函数模板不同的是,编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在模板名后的尖括号中提供额外信息——用来代替模板参数的模板实参列表。
template <typename T> class Foo {
// 类模板实现
};
使用一个类模板时,我们必须提供显式模板实参列表。它们被绑定到模板参数,编译器使用这些模板实参来实例化出特定的类。
// 实例化出Foo<int>类
Foo<int> fi;
// 实例化出Foo<double>类
Foo<double> fd;
需要注意的是,如果一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化,这一特性使得即使某种类型不能完全符合模板操作的要求,我们仍然能用该类型实例化类。
与其他任何类相同,我们既可以在类模板内部也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式声明为内联函数。但是定义在类模板之外的成员函数必须以关键字template
开始,后接类模板参数列表。
template <typename T>
ret-type Foo<T>::member-name(parm-list)
当我们在类模板外定义其成员时,由于我们并不在类的作用域中,直到遇到类名才表示进入类的作用域,因此我们在使用类模板名时必须重复模板实参。在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参:
template <typename T>
Foo<T> Foo<T>::bar() { // 类外使用类模板名时必须重复模板实参
Foo ret = *this; // 类内使用类模板名不必指定模板实参, 等价于Foo<T> ret = *this;
return ret;
}
当一个类包含一个友元声明时,类和友元各自是否是模板是相互无关的。如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例。如果友元自身是模板,则类可以授权给所有友元模板实例,也可以只授权给特定实例。
类模板与另一个(类或函数)模板间友好关系的最常见形式是建立对应实例及其友元间的友好关系。
// 前置声明
template <typename T> class Foo;
template <typename T> class FooPtr;
template <typename T> bool operator==(const Foo<T>&, const Foo<T>&);
template <typename T> class Foo {
// 每个Foo实例将访问权限授予相同类型实例化的FooPtr和相等运算符
friend class FooPtr<T>;
friend bool operator==<T>(const Foo<T>&, const Foo<T>&);
};
Foo<int> fi; // FooPtr<int>和operator==<int>都是本对象的友元
一个类也可以将另一个模板的每个实例都声明为自己的友元,或者限定特定的实例为友元:
// 前置声明
template <typename T> class Pal;
// C是一个普通的非模板类
class C {
// 用类C实例化的Pal是C的友元
friend class Pal<C>;
// Pal2的所有实例都是C的友元, 这种情况下无须前置声明
template <typename T> friend class Pal2;
};
// C2是一个类模板
template <typename T> class C2 {
// C2的每个实例都将相同相同实例化的Pal声明为友元
friend class Pal<T>;
// Pal2的所有实例都是C2每个实例的友元, 不需要前置声明
template <typename X> friend class Pal2;
// Pal3是一个非模板类, 它是C2所有实例的友元, 不需要前置声明
friend class Pal3;
};
在C++11新标准中,我们可以将模板类型参数声明为友元:
template <typename T> class Foo {
// 将模板类型参数声明为友元
friend T;
};
C++11新标准允许我们为类模板定义一个类型别名:
// authors的类型是pair<string, string>
template<typename T> using twin = pair<T, T>;
twin<string> authors;
当我们定义一个模板类型别名时,也可以固定一个或多个模板参数:
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; // books类型是pair<string, unsigned>
partNo<Student> kids; // kids类型是pair<Student, unsigned>
Tips:类似任何其他成员函数,一个
static
成员函数只有在使用时才会实例化。
与任何其他类一样,类模板可以声明static
成员:
template <typename T> class Foo {
public:
static size_t count() { return ctr; }
private:
static size_t ctr;
};
// 定义并初始化ctr成员
template <typename T>
size_t Foo<T>::ctr = 0;
// 使用static成员
Foo<int> fi; // 实例化Foo<int>类和static成员ctr
auto ct = Foo<int>::count(); // 实例化Foo<int>::count
ct = fi.count(); // 使用Foo<>
ct = Foo::count(); // 错误: 没有指定哪个模板实例的count
每个Foo
的实例都有一个static
成员实例,即对任意给定类型X
,都有一个Foo<X>::ctr
和一个Foo<X>::count
成员,所有Foo<X>
类型的对象共享相同的ctr
对象和count
函数。