前言
本篇学习C++中的泛型编程之二,类模板。
类模板
类模板与函数模板类似,用于指导编译器生成类,因此也有模板实例化和具体化。
定义类模板
使用template<typename T>
模板头,提示编译器是一个模板。
template<typename T>
class TemplateClass{
private:
T a_;
T b_;
public:
TemplateClass(T a, T b);
T sum();
};
template<typename T>
TemplateClass<T>::TemplateClass(T a, T b){
a_ = a;
b_ = b;
}
template<typename T>
T TemplateClass<T>::sum(){
return a_ + b_;
}
类模板声明需要注意:
- 成员名不能与模板参数名相同;
- 模板参数可以有多个,
template<typename T1, typename T2>
; - 模板可以拥有非类型参数,
template<typename T, int n>
,但非类型参数不能是浮点型; - 不同类模板或函数模板的模板参数名可以重复使用。
类模板定义需要注意:
- 在外部定义成员函数时时,需要添加模板头
template<typename T>
和类限定符TemplateClass<T>::
; - 如果在类内定义成员函数(内联),则可省略模板头和类限定符。
模板具体化
从类模板生成实际类的过程就是具体化,包括实例化,显式具体化和部分具体化。
隐式实例化
使用模板类创建指定模板参数类型的对象,就是隐式实例化。
TemplateClass<int> tc(2, 3);
注意:模板类隐式实例化必须显式指定模板类型,这点与函数模板不同。
显式实例化
使用关键字template
指出类模板的参数类型,就是显式实例化。
template class TemplateClass<double>;
显式具体化
如果针对某种类型的类需要不同的实现,比如int类与int*类明显实现方法不一样,则可以通过显式具体化指定特定类型的生成:
template <> class TemplateClass<int*>{
// ...
};
部分具体化
还可以在多模板类型参数中的指定某个参数的具体类型:
template <typename T1, typename T2> class TTC{ // general
// ...
};
template <typename T1> class<T1, int> TTC{ // partial
// ...
};
模板编译
类模板和函数模板尽量使用包含编译,即把模板声明和定义放在同一个头文件中,需要使用模板的文件包含模板头文件。
分离编译可能导致链接错误。这是由于,分离编译时,编译器单独把每个cpp文件编译成单独的.obj文件,然后链接成.exe文件。
模板在分离编译时,由于在模板.cpp文件中没有被调用,没有生成模板实例,因此在链接时其它文件找不到类和函数实例,即报链接错误。
模板成员
模板也可以作为类成员:
template<typename T> class C{
public:
template<typename U>
U print();
};
模板与友元
非模板友元
非模板友元是一个常规函数:
template<typename T> class D{
public:
T a_;
friend void printd(D<T>&);
};
void printd(D<int>& d){
std::cout << d.a_;
}
注意:使用非模板友元时,编译器将报一个警告warning: friend declaration declares a non-template function
。
约束模板友元
友元函数是个函数模板,函数模板参数类型与类模板相同:
template<typename T>
void printd(T &);
template<typename T>
class D{
public:
friend void printd<D<T>>(D<T>&);
};
template<typename T>
void printd(T& d){
// ...
}
约束模板友元:
- 首先声明友元函数的模板
- 然后声明类模板
- 为友元函数模板提供定义
非约束模板友元
函数模板的参数类型与类模板不同:
template<typename T>
class D{
public:
template<typename U>
friend void print(U&);
};
template<typename T>
void print(T&){
// ...
}