模板是C++泛型编程的基础,一个模板就是一个创建类或者函数的公式;在具体使用时,我们需要将模板参数转化为具体类型,比如int,float,Vector以及自定义类型;这种转换过程发生在编译阶段。
定义模板
假如我们需要完成一个数值幂次方函数的功能,这个数组可以是int,也可以是float;我们的函数分别如下:
整型值的幂次函数:
int Power(int base, int index)
{
if (0 == index) return (int)1;
if (0 > index) return(int)0;
int result = base;
while (--index)
{
result *= base;
}
return result;
}
浮点型的幂次函数:
float Power(float base, int index)
{
if (0 == index) return (float)1;
if (0 > index) return(float)0;
float result = base;
while (--index)
{
result *= base;
}
return result;
}
可以发现这两个函数基本一样,唯一的差异就是参数和返回值类型不同,函数体完全一致;若还需要针对long型,long long型,我们需要写两份几乎一样的代码。
针对这种情况,C++给出了模板技术,代码编写阶段类型先不确定;在编译器编译时根据函数入参类型再确定,从而生成具体的函数版本。
函数模板
函数模板定义
函数模板的定义如下,由template关键字,一对尖括号组成,T是由typename 标识的不确定类型,编译器会在编译阶段生成具体的函数版本。
template<typename T>
T Power(T base, int index)
{
if (0 == index) return (T)1;
if (0 > index) return(T)0;
T result = base;
while (--index)
{
result *= base;
}
return result;
}
实例化函数模板
当我们调用一个函数模板时,编译器会根据函数实参来推断模板的具体类型,然后根据这个类型实例化这个类型的版本函数。比如:
(1) cout << "power(2,3):" << Power(2,3) << endl;
(2) cout << "power(2.1,3):" << Power(2.1f,3) << endl;
编译器在编译时,遇到语句(1),可以推断出模板实参的类型为int,编译器则开始生成入参为int型的函数版本;同理,遇到语句(2)则生成入参为float型的函数版本;
它们的函数声明如下:
(版本一)int Power(int base, int index)
(版本二)float Power(float base, int index)
类模板
类模板(Class template)是用来生成类的蓝图,其原理基本和函数模板基本一致;所不同的是,使用类模板时,我们必须显示指定类模板的类型信息。
类模板定义:
类模板的定义如下:
template <typename T>
class CThree
{
public:
CThree(T a, T b, T c) ;
T Min();
T Max();
private:
T m_a;
T m_b;
T m_c;
};
类模板实现:
需要注意的是, 每个成员函数前面都要加上 template< class T >,而且类的名称应该使用CThree< T >.示例代码如下:
template<class T>
T CThree<T>::Min()
{
T MinVal = m_a < m_b ? m_a : m_b;
return MinVal < m_c ? MinVal :m_c;
}
template<class T>
T CThree<T>::Max()
{
T MaxVal = m_a > m_b ? m_a : m_b;
return MaxVal > m_c ? MaxVal :m_c;
}
template<class T>
CThree<T>::CThree(T a, T b, T c):
m_a(a),
m_b(b),
m_c(c)
{
return;
}
实例化类模板
类模板和函数模板一样,也存在实例化的过程,当编译器遇到语句(1)(2)时,编译器则生成int型和float型的类版本,这两个版本是相互独立的类,不会相互影响。
(1) CThree<int> IntInstance(1,2,4);
(2) CThree<float> FloatInstance(1.1f,2.2f,4.4f);
模板编译与链接
现在就来看看,编译器对模板是如何编译和链接吧;
当编译器遇到一个template时,不能够立马为他产生机器代码,它必须等到template被指定某种类型。也就是说,函数模板和类模板的完整定义将出现在template被使用的每一个角落,比如遇到上述中的4个语句时,才能确定编译内容,否则编译器没有足够的信息产生机器代码。
对于不同的编译器,其对模板的编译和链接技术也会有所不同,其中一个常用的技术称之为Smart,其基本原理如下:
1. 模板编译时,以每个cpp文件为编译单位,实例化该文件中的函数模板和类模板
2. 链接器在链接每个目标文件时,会检测是否存在相同的实例;有存在相同的实例版本,则删除一个重复的实例,保证模板实例化没有重复存在。
比如我们有一个程序,包含A.cpp和B.cpp,它们都调用了CThree模板类,在A文件中定义了int和double型的模板类,在B文件中定义了int和float型的模板类;在编译器编译时.cpp文件为编译基础,生成A.obj和B.obj目标文件,即使A.obj和B.obj存在重复的实例版本,但是在链接时,链接器会把所有冗余的模板实例代码删除,保证exe中的实例都是唯一的。编译原理和链接原理,如下所示: