模板实例化
程序员在使用模板类最常犯的错误为:将模板类视为某种数据类型。所谓类型参量化这样的术语导致了这种误解。模板当然不是数据类型,模板就是模板。
明确以下观点,帮助理解模板:
- 编译器使用模板,通过模板参数来创建数据类型,这个过程就是模板实例化;
- 从模板类创建得到的类型称之为特例;
- 模板实例化取决于编译器能够找到可用代码来创建特例;
- 要创建特例,编译器不仅要看到模板的声明,还要看到模板的定义;
- 模板实例化的过程是迟钝的,即:只能通过函数的定义来实现实例化。
方法一定义模板
// array.h
template <typename T, int SIZE>
class array
{
T data_[SIZE];
array (const array& other);
const array& operator = (const array& other);
public:
array(){}
T& operator[](int i) {return data_[i];}
const T& get_elem (int i) const {return data_[i];}
void set_elem(int i, const T& value) {data_[i] = value;}
operator T*() {return data_;}
};
方法二定义模板
// array.h
template <typename T, int SIZE>
class array
{
T data_[SIZE];
array (const array& other);
const array& operator = (const array& other);
public:
array(){}
T& operator[](int i);
const T& get_elem (int i) const;
void set_elem(int i, const T& value);
operator T*();
};
// array.cpp
#include "array.h"
template<typename T, int SIZE> T& array<T, SIZE>::operator [](int i) {
return data_[i];
}
template<typename T, int SIZE> const T& array<T, SIZE>::get_elem(int i) const {
return data_[i];
}
template<typename T, int SIZE> void array<T, SIZE>::set_elem(int i, const T& value) {
data_[i] = value;
}
template<typename T, int SIZE> array<T, SIZE>::operator T*() {
return data_;
}
模板的使用
// main.cpp
#include "array.h"
int main(void)
{
array<int, 50> intArray;
intArray.set_elem(0, 2);
int firstElem = intArray.get_elem(0);
int* begin = intArray;
}
对于上述两种定义模板的方式:方法一可以正常编译运行,但是方法二编译时会出现三个错误。
array
是一个模板,array<int, 50>
是一个模板实例。从array
创建array<int, 50>
的过程就是实例化过程。实例化要素在main.cpp文件中。如果按照传统方式,编译器在array.h中看到了模板的声明,但是没有模板的定义,这样编译器就不能创建array<int, 50>
。但这时并不出错,因为编译器认为模板定义在其他文件中,就把问题留给链接器处理。于是,编译array.cpp时会发生什么问题呢?编译器可以解析模板定义并检查语法,但是不能生成成员函数的代码,因为要生成代码需要知道模板参数,这样链接程序在main.cpp或者array.cpp中都找不到array<int, 50>
的定义,于是报出无法定义成员的错误。但是为什么是三个错误呢?这时由于实例化的惰性导致的。main.cpp中还没有用到operator[],编译器还没有实例化它的定义。
解决方法:
- 在实例化要素的过程中要让编译器看到模板定义。
- 用另外的文件来显式的实例化类型,这样链接器就能看到该类型。
- 用export关键字。
前两种方法通常称为包含模式,第三种方法称为分离模式。第一种方法意味着在使用模板的转换文件中不仅要包含模板声明文件,还要包含模板定义文件。在上例中,第一个示例在array.h中用行内函数定义了所有成员函数。或者在main.cpp中也包含进array.cpp文件。这样编译器就可以看到模板的声明和定义,并由此生成array<int, 50>
实例。这样的缺点是:编译文件会变得很大,显然会降低编译和链接速度。
第二种方法:通过显式的模板实例化得到类型。最好将所有的显式实例化安放在另外的文件中。在本例中,可以创建一个新的文件templateinstantiations.cpp
//templateinstantiations.cpp
#include "array.cpp"
template class array<int, 50>;//显式实例化
array<int, 50>
类型不是在main.cpp中产生的,而是在templateinstantiation.cpp中产生。这样链接器可以找到它的定义。这种方法不会产生巨大的头文件,加快编译速度,而且头文件本身也会显得更加“干净”和更具有可读性。但是这个方法不能得到惰性实例化的好处,即:它将显式的生成所有的成员函数。另外,还要维护templateinstantiation.cpp文件。
第三种方法实在模板定义中使用export关键字,剩下的由编译器自行处理了。(Stroustrup的书中的export关键字,export几乎不能解决用包含模式能够解决的问题,Herb Sutter的文章讲了相关内容)很遗憾,目前VC、VS的任何版本皆不支持分离模式。
模板类不是所谓的“原始数据类型”,要用其他的编程思路。