文章目录
概述
C++中使用类模板一般的格式是:
template<typename T, ...>
class TemplateClass {
...
};
其中,第一行的template<…>是声明这是一个模板类。具体实现的时候可能有一个模板参数,也可能有多个,可以有类类型,也可以有非类类型。还可以有默认的模板实参。
带默认模板实参的情形 (Default Template Arguments)
template<typename T, typename V = char>
class C {
...
};
上面的代码中typename V = char就是属于有默认模板实参的。
特化与偏特化
模板的偏特化/部分特化/局部特化,中文翻译有很多,英文原文是 Partial Specialization。
而特化的原文就是 Specializations
template<typename T, typename T2>
class C {
...
};
偏特化 (Partial Specialization)
偏特化上面的模板有两类,其一是:
template<typename T, typename T>
class C {
...
};
也就是说正好这两种类型一样。
其二是:
template<typename T, int>
class C {
...
};
也就是说某一种模板类型具体化了。
特化 (Specializations)
如果是特化上面的模板类,那么特化的方式之一可以如下:
template<>
class C<std::string, int> {
...
};
注意,特化的标志就是template<>。
特化与偏特化在模板匹配的时候优先级要高于模板,一般是要针对一些(被特化的)类型想要做一些特别的事情,比如优化内存管理。
写模板一定会过的坎 – 链接错误
当我们开始写一个类模板的时候,比如:
首先写一个类的头文件:
// class T header file -- foo.h
template<typename T>
class Foo {
public:
void print_msg();
...
};
...
然后写一个源文件:
// class T source file -- foo.cpp
#include "foo.h"
template<typename T>
void Foo<T>::print_msg() {
}
然后在另一个地方使用上面的类:
// may be main.cpp
#include "foo.h"
void entry(){
Foo<int> o;
o.print_msg();
}
结果在编译的时候发现编译环节没有问题,但是链接的时候发现有错误,提示找不到Foo::print_msg()的定义。
这是因为使用模板的地方只是包含了foo.h文件,但是还没有对Foo模板进行int的实例化,原因下面慢慢道来。
我们一般的类,比如定义了一个SimpleFoo,分开定义在一个头文件,一个源文件中,编译的时候我们能够唯一确定两个文件说的是一个类型,所以链接的时候没有问题。
但是对于模板而言,必须要等到实例化的时候才能确定下具体的类型,比如说Foo和Foo两种类型,即使short可以隐式地转化为int,但是这对于模板来说仍然是两种截然不同的类型。而没有实例化的模板,根本没有任何编译好的代码。所以上面那种出错的情况找不到实现代码也就不奇怪了。一个更加详细的解释参考:Splitting templated C++ classes into .hpp/.cpp files–is it possible? in StackOverflow
解决的办法,一般说是有三种(包含模型):
- 将所有代码写在头文件中
- 在头文件的结尾include源文件
- 在所有引用该模板的地方包含源文件
但是,实践下来发现,第二种方案在MSVC 2015 上编译不过,第三种做法太烂没见过这种做法,只有第一种可行。不过第一种方法会有两个问题:造成编译时间变长,代码体积变得更大。
在具体写带默认模板参数的过程中还发现一个问题:
template<typename T, typename t1 = T::SubType>
class Foo{
...
};
上面的代码在MSVC 2015上编译没有问题,但是在Xcode(clang++)上编译出现错误,错误的点是typename t1 = T::SubType。后来更具提示修改为:
template<typename T>
class Foo{
using t1 = typename T::SubType;
...
};
就在两个环境下都正确了。注意一定要在using那句中添加typename标志,不然编译不过。