模板声明与定义的关系
好久不用C++了,前几天写了一个模版,按以前的习惯,把定义写在头文件中, 把实现写在cpp文件中。结果在编译的时候没有发生错误,但在连接的时候出现了找不到xxx的错误。一时之间不知道该怎么办才好,后来上网一查,自己再一想,唉,原来如此:
模版之所以为模版,就是可以容纳不同的类型嘛,所以它只有在指定了其类型的时候才会生成实际的代码,如果你只把模版的声明写在头文件中,那么你生成在使用模版的时候就只会有声明部分,而没有其具体的实现,所以链接器在连接时会报错。
解决的办法有两个:
1. 把模版的声明和实现都写在一个头文件中,然后用的时候包含这个头文件。
2. 如果你已经把它们分开了,比如说xxx.h xxx.cpp。那也好办,只好在使用的时候#include “xxx.h” #include “xxx.cpp”即可。
###################################################################################################
模板特化
模板是针对泛型编程的,泛型即不针对特定类型,所有类型都可以套用,而且是编译阶段由编译器完成由泛型到特定类型的转换,编译器实际上做的就是把你制定的类型替换到模板中的模板类型,然后再执行编译连接。
那么这时候会出现某种问题,当某一特定类型跟其他类型在某些操作上有不同的特性时,泛型编程这边就很难做。
举个例子,一个compare()函数:
template<typename T>
int compare(const T&v1, const T&v2)
{
if(v1 < v2) return -1;
if(v2 < v1)return 1;
return 0;
}
上面这个compare的模板实现,对int、long、float、char等类型来说都没问题,但对于指针类型如const char*类型时则有问题,因为模板函数的实现中这样比较的话,当类型被代换为const char*时,比较的是两个指针本身值的大小,而不是两个指针所指向内容的大小。
那该如何解决这个问题呢,能否写出一个模板它可以兼容常规类型(对某特定操作具有相同性质)又同时可兼容某些特定类型(对某些特定操作具有不同的性质)呢?
模板特化出场了。
模板特化有点类似函数重载的那种思想,首先写出能兼容大多数类型的模板体,然后再针对某些特定类型再重写特殊的实现体,该实现体指明了只针对这些特定类型。然后编译器在编译时,遇到模板调动后先匹配模板特化,如果模板特化未匹配中再匹配模板。这样就实现了可以自由的兼容所有类型(有多少类型跟大多数类型不一样,就实现多少个针对这些类型的模板特化)
如上面那个compare模板函数,可以实现一个针对const char*的模板特化:
//模板特化可以理解为针对模板的补充,那么自然模板(非特化模板)是不可或许的,如果没有模板放在前面,那么是不好理解模板特化的意义的
//(因为你如果不知道特化是针对模板的补充的话,你完全有理由问“为什么不干脆声明为普通函数呢,这个模板既然已经指定了类型,那还有啥作用?”
template<typename T>
int compare(const T&v1, const T&v2)
{
if(v1 < v2) return -1;
if(v2 < v1)return 1;
return 0;
}
//对compare函数的一个特化
template<>
int compare<const char*>(const char* const &v1, const char* const &v2)
{
return strcmp(v1, v2);
}
注意,特化的声明必须与对应的模板完全匹配。
有了函数模板的特化,自然也有类模板的特化。二者差别不大,不过要说一下的是如果需求中需要特化某类模板,可以考虑将真个类模板特化,也可以考虑将只该类模板中某些必须特化的接口给特化(部分特化),如vector和queue容器针对const char*类型时所作的特化,可以仅特化push和pop两函数。
另外,若某模板有多个模板形参,那么将它全部的形参都特化这种方式称为全特化,将它的部分形参特化的方式称为偏特化。
如这里:
template<class U,class T>
class C{};
全特化:
template<>
class C<int,char>{};
偏特化:
template<class U>
class C<U,int>{};