参考读物:
《Thinking in C++: vol1 & vol2》 Bruce Eckel -
www.bruceeckel.com
《C++ Template》 David Vandevoorde, Nicolai M. Josuttis
推荐读物:
《modern C++ design》Andrei Alexandrescu
1 介绍
模板是一个包含有未指定类型的函数或类,因此模板并不是一个真正的函数或类,而是代表了一组函数或类,当为模板函数或类指定了一种类型时,就生成了此模板的一个实例,这个操作叫做模板
实例化(instantiation)。也可以为某一种类型提供不同于模板的定义,这个称为
特化定义(specialization)。对于有多个类型参数的模板,还可以只指定一部分类型,这个称为
偏特化定义(partial specialization)。
模板不是实体,因此模板的声明和定义通常都放在头文件中。
2
函数模板
2.1 声明
template<class T>
inline void func(T param)
{
inline void func(T param)
{
}
2.2 函数模板不支持默认类型参数,但支持函数的默认参数
template<class T/* = type,不可以 */>
template<class T/* = type,不可以 */>
void func(T param, int size = sizeof(T))
{
}
2.3 函数模板显式实例化声明
template void func<int>(int param);
显式实例化后的函数模板不能再有不能转换的类型的调用
显式实例化后的函数模板不能被特化定义
template void func<int>(int param);
显式实例化后的函数模板不能再有不能转换的类型的调用
显式实例化后的函数模板不能被特化定义
2.4 特化定义函数模板
template<>
template<>
void func<int>(int param) //函数模板只支持全特化
{
}
特化后是一个实体函数,不再是模板,放在头文件中会导致重定义
2.5 函数模板重载
template<class T>
template<class T>
void func(T param, int i)
{
}
template<>
void func(ClassName param, int i)
{
}
void func(int i)
void func(int i)
{
}
这几种func函数的定义,匹配的顺序是非模板函数 -> 特化函数模板 -> 基函数模板。如果不像func<int>(100)这样指定函数模板的参数类型的话,编译器会为函数模板推测出一种类型,如根据100为int类型,编译器会自动调用func<int>,称为隐式特化,而前者则成为显式特化。
为什么函数模板要特化呢?这是因为有时候函数模板并不能处理所有情况,对于个别情况就可以使用特化来替换掉原来模板。
3
类模板
3.1 声明
template<class T>
class ClassName
{
};
3.2 类方法类外定义
template<class T>
template<class T>
ReturnType ClassName<T>::Func()
{
};
3.3 非类型(nontype)模板参数
如template<size_t _Nb> class bitset {}就是使用size_t类型参数设置位的个数。
如template<size_t _Nb> class bitset {}就是使用size_t类型参数设置位的个数。
可以是常量整数类型(包括枚举)或外部链接(external linkage)对象的指针或引用作为模板参数,不能是浮点型和类对象。
char *s = "hello"; //s不可以,直接使用"hello"亦不可以,因为两个"hello"可能为不同地址
char s[] = "hello"; //s可以
ClassName obj; //obj可以
ClassName<int> obj; //obj可以
char s[] = "hello"; //s可以
ClassName obj; //obj可以
ClassName<int> obj; //obj可以
什么是一个外部链接对象呢?
关于这个问题,专门转载了SpitFire同志的一篇文章《
内部链接和外部链接》到博客中
总的说来,要作为参数必须满足:1、在编译时和链接时可以求值; 2、这个参数如果是指针,则它所指的变量,如果在两个cpp中定义会出链接错误。
不同的参数值构成不同的类型,bitset<100>和bitset<200>是两个类型
3.4 默认参数
template<class T = int, int param = 100>
class ClassName
{
};
3.5 以模板为类型参数的类模板声明
template<class T>
template<class T>
class Array;
template<class T1, template<class /* 可以省略类型名 */ > class T2>
template<class T1, template<class /* 可以省略类型名 */ > class T2>
class ClassName
{
T2<T1> m_o;
}
如果T2是带默认参数的:template<class U, int i = 100> class T2; 则在以它为类型参数的类中必须再次指明默认值,如果两个默认值不同以再次声明的为准。
template<class T, int i = 100>
class Array;
template<class T1, template<class, int = 100> class T2>
template<class T1, template<class, int = 100> class T2>
class ClassName
{
T2<T1> m_u;
}
使用:
ClassName<int, Array> obj;
使用:
ClassName<int, Array> obj;
3.6 特化模板类
template<class A, class B>
template<class A, class B>
class ClassName
{
};
3.6.1 全特化
template<>
template<>
class ClassName<int, double>
{
}; //特化后是一个实体类,不再是模板
3.6.2 偏特化
template<class B>
class ClassName<int, B>
{
}; //特化后是一个实体类,仍是模板
template<class A, class B>
class ClassName<A*, B*>
{
};
3.6.3 类成员函数特化
template<class T>
class ClassName
{
public: void f();
};
template<>
template<>
inline void class ClassName<int> :: f()
{
} //特化类ClassName<int>的f(),T为int类型时优先调用此定义
3.7 模板类里的静态变量
template<class T>
class ClassName
{
static T a;
}
定义:
int ClassName<int>::a = 100; /* 定义一个与类模板的类型参数对应的静态变量 */
main()
{
ClassName<int> o;
}
3.8 typename关键词
3.8.1 表明紧跟在后面的是类型,而不是其他(如静态变量)
typename vector<T>::iterator it;
3.8.2 定义新类型
typedef typename vetor<T>::iterator Iterator_Type;
3.8.3 代替模板中的class关键词
template<typename T>
class ClassName
{
};
3.9 模板类中的成员函数模板
template<class T>
template<class T>
class ClassName
{
public:
template<class T1, class T2> T m_func(T1 a, T2 b);
}
public:
template<class T1, class T2> T m_func(T1 a, T2 b);
}
类外定义:
template<class T>
template<class T1, class T2>
T ClassName<T>::m_func(T1 a, T2 b)
{
}
特化
template<>
template<>
int ClassName<int>::m_func(int a, int b) //类必须被一起全特化
{
}
调用:
ClassName<int> o;
o.template m_func<int, vector<int> >(100, vector<int>()); //调用时实例化
ClassName<int> o;
o.template m_func<int, vector<int> >(100, vector<int>()); //调用时实例化
3.10 继承模板类
template<class T>
template<class T>
class Chlid : public Parent<T>
{
};
3.10.1 特化继承
class ClassName : public vector<int>
class ClassName : public vector<int>
{
};
3.11 explicit关键字
explicit Y(const X& x);
X x; Y y(x); //显式转换
X x; Y y = x; //隐式转换,编译不通过
explicit Y(const X& x);
X x; Y y(x); //显式转换
X x; Y y = x; //隐式转换,编译不通过
第一条语句通过使用显式从X类型转换生成了一个Y类型的对象,后一条语句则使用了隐式转换创建。由于使用了explicit要求必须使用显式,所以编译没有通过。
4 总结
模板加强了使用C++编写可复用代码的能力,但是想学好模板需要花费很大的功夫和精力的,而最难的就是将模板和原有C++面向对象的技术,如继承和多态等,相互融会贯通。