c++模板函数
模板特化(也有翻译为模板具体化)(specialization)
如果把模板函数当作数学归纳法的话,模板特化就是n=常数C的情况。
//模板函数声明
template <typename T>
bool greater(const T &a,const T &b);
int main()
{
return 0;
}
//模板函数定义
template <typename T>
bool greater(const T &a,const T &b)
{
if(a > b)
return true;
else
return false;
}
如果上面的T是char*类型,那么这种比较是不符合我们要求的,它比较的是地址,char*应该用strcmp。
如果按照我们最简单的思路的话就是直接将T换成char*,但这样编译却给出了error,说这个特化找不到对应的模板函数。
#include <cstring>
//模板函数声明
template <typename T>
bool greater(const T &a,const T &b);
//特化的模板函数的声明
template<>
bool greater<char*>(const char * &a,const char * &b);
int main()
{
return 0;
}
//模板函数定义
template <typename T>
bool greater(const T &a,const T &b)
{
if(a > b)
return true;
else
return false;
}
//特化的模板函数的定义
template<>
bool greater<char *>(const char * &a,const char * &b)
{
return strcmp(a,b)>0?true:false;
}
我们模板函数的定义const T &a
,其实也可以是这种写法T const &a
,这两种写法表明const和T分别修饰a,也就是a的类型既是const
又是T
;而不是const T作为一个整体,a的类型是const T
。虽然在这个问题上,这两种理解没有出现语义的误解,但是在上面的例子就出现误解了。
根据我们原来的模板函数的定义,我们的目的应该是这样的特化函数的参数,`const (char *) &a`(是没有这种写法,只是为了让我们理解方便),也就是说char \*是一个整体,和const一起修饰a。`const char* &a`却让`const char`作为了一个类型,这显然不是我们要的。`char* const &a`这才是我们想要的。
#include <cstring>
//模板函数声明
template <typename T>
bool greater(T const &a,T const &b);
//特化的模板函数的声明
template<>
bool greater<char*>(char * const &a,char * const &b);
int main()
{
return 0;
}
//模板函数定义
template <typename T>
bool greater(T const &a,T const &b)
{
if(a > b)
return true;
else
return false;
}
//特化的模板函数的定义
template<>
bool greater<char*>(char * const &a,char * const &b)
{
return strcmp(a,b)>0?true:false;
}
如果在写类型时,习惯将const放在类型char等后面,那么写模板特化时,直接代换T就不会出现错误。
定义与实现分离?
在传统上,我们总是把定义写在.h文件里,而实现文件写在.cpp文件里,但这在模板里面是否可以,请看下面一个例子。
//swap.h
template <typename T>
void swap(T &a,T &b);
//swap.cpp
template <typename T>
void swap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
//main.cpp
#include "swap.h"
int main()
{
int a,b;
swap(a,b);
//...
return 0;
}
编译器在编译main.cpp文件时只看到swap的声明而没有定义(实现),所以留空,让连接器去链接swap的实现。而编译器在编译swap.cpp时没有看到模板参数,所以也不会编译,最后便会导致链接错误,即找不到swap<int>。
所以模板函数或者模板类,应该将定义和实现放在.h文件。
显示实例化(explict initialization)
实例化:一个通过使用具体值替换模板参数,从模板产生的普通类,函数或者成员函数的过程。
隐式实例化:这是编译器看到模板函数时,在当前文件实现相应的模板参数的实例化。
显示实例化:就是自己手工让编译器在此文件实现相应的模板参数的实例化。
既然编译器会自动实现实例化,为什么还要我们去手工去让编译器实现实例化呢?请看下面的例子。
//swap.h
template <typename T>
void swap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
//function.h
void function();
//function.cpp
#include "swap.h"
void function()
{
int a,b;
swap(a,b);
}
//main.cpp
#include "swap.h"
#include "function.h"
int main()
{
int a,b;
swap(a,b);
function();
//...
return 0;
}
为了容易看出,我们暂且用function表示一个用到swap的函数,它也可以是一个类。
编译器在编译function.cpp时会用void swap<int>(int &a,int &b),故生成的obj文件有void swap<int>(int &a,int &b)的二进制代码,同理,在main.cpp生成的obj文件里也有void swap<int>(int &a,int &b)的二进制代码。也就是说最后生成的exe有两个相同的二进制代码。
为了减少代码,我们可以用一个cpp文件(就起名为explict_initilization.cpp)手动显示实例化这些模板类,如下。这样的话,链接器最后会将main.cpp和function.cpp中的swap<int>链接到explict_initilization.cpp。
//swap.h
template <typename T>
void swap(T &a,T &b);
//swap
templatee <typename T>
void swap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
//explict_initilization.cpp
#include "swap.cpp"
template void swap<int>(int&,int&);//显示实例化
//function.h
void function();
//function.cpp
#include "swap.h"
void function()
{
int a,b;
swap(a,b);
}
//main.cpp
#include "swap.h"
#include "function.h"
int main()
{
int a,b;
swap(a,b);
function();
//...
return 0;
}
更加详细的介绍可以看:c++模板类(一)理解编译器的编译模板过程
export的用法
为了解决上面用一个explict_initilization.cpp来管理实例化文件的不便利,c++可以export这个关键词,让编译器和链接器去管理我们上面要解决的事情。
//swap.h
export
template <typename T>
void swap(T &a,T &b);
//swap
templatee <typename T>
void swap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
//function.h
void function();
//function.cpp
#include "swap.h"
void function()
{
int a,b;
swap(a,b);
}
//main.cpp
#include "swap.h"
#include "function.h"
int main()
{
int a,b;
swap(a,b);
function();
//...
return 0;
}
通过export这个关键字,即使在模板定义不可见的条件下,被导出的模板也可以正常使用,当编译器看到export这个关键字,会自动去搜寻模板的定义。
这个看似很美好,但是在export这个关键字推出到2011年,很少有编译器去实现这个关键字的功能,所以在C++11到C++20这段期间被作为了一个保留字,C++20又被赋予了新的意义。