有很多方法可以组织我们的模板template程序代码,本文介绍常见的一种方式:置入式模型。
1. 链接错误(Link Error)
大多数C,c++程序员大致按以下方式来组织他们的non-template程序代码:
Classes和其他类型被全体放置于头文件(header files)。通常头文件的后缀名称(扩展名)为.hpp(或.H, .h,.hh,.hxx 等等)。
全局变量和non-inline函数只在头文件中置入声明语句,定义式则置于.c文件中。这里的.c文件是个统称,通常其后缀名称(扩展名)为.cpp(或.c,.C,.cc,.cxx等等)。
这种方式运作良好,程序能够轻易找到各个类型的定义,并避免一个变量或函数被多次重复定义,于是连接器(linker)可以正常工作。
但是模板程序代码在这里跟非模板程序不一样,举例来说,我们把 template声明于某个头文件:
//myfirst.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
//template 声明语句
template <typename T>
void print_typeof (T const&);
#endif
print_typeof()是一个简单的辅助函数,它打印参数的类型信息(type information)。函数的实际代码被置于一个.c文件中:
//myfirst.cpp
#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"
//template定义式
template <typename T>
void print_typeof (T const& x)
{
std::cout << typeid(x).name() << std::endl;
}
这个例子中使用typeid运算符,把参数的类型以字符串形式打印出来。
此后,我们又在另外一个c文件中使用这个template,并将template声明语句(所在的文件)以
#include包含进来:
//main.cpp
#include "myfirst.hpp"
int main()
{
double ice=3.0;
print_typeof(ice);
}
大多数编译器可以正确编译上述代码,但会报告一个链接错误,表示无法找到print_typeof()函数的定义。
错误的原因在于,function template print-typeof()的定义没有被实例化。为了实例化一个模板,编译器必须知道以哪一份定义式以及哪些模板参数对它实例化。不幸的是先前这个例子中,这两项信息被分置在两个分开编译的文件中。因此,当编译器看到对print_typeof()的调用时,看不到其定义,无法以double类型来实例化print_typeof()。于是编译器假设这个template的定义位于其他某处,因而只生成一个对该定义的reference,并将这个reference所指的定义式留给连接器去解决(resolve)。另一方面,当编译器处理myfirst.cpp时,它又察觉不到该以哪个自变量类型来实例化其定义式。
2. 把模板放在头文件中(Header Files)
就像处理宏macro和内联函数inline一样,解决上面问题的常见办法是:把template定义式放到其声明语句所在的头文件中。面对前例,我们可以在myfirst.hpp最后一行加入:
#include “myfirst.cpp”
也可以在用到该template的每一个.c文件中#include “myfirst.cpp”.
第三种方法,也是最常用的方法,完全不用myfirst.cpp,声明和定义都写在文件myfirst.hpp中:
//myfirst.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
#include <iostream>
#include <typeinfo>
// 声明语句
template <typename T>
void print_typeof (T const &)
//定义语句
template <typename T>
void print_typeof(T const& x)
{
std::cout << typeid(x).name() << std::endl;
}
#endif
这种template组织法称为置入式模型inclusion model。现在上面程序可以正确编译、链接与运行。
需要引起注意的是:
包含myfirst.hpp的代价比较大。此物所谓代价,不仅指template定义式的体积增大,也包括含入myfirst.hpp中其他的文件,比如<iostream>和<typeinfo>。我们会发现,可能导致成千上万的代码被包含进来,因为诸如<iostream>也是以此种方式来定义template的。
尽管存在上面的问题,还是强烈推荐:只要有可能,尽量用置入式模型inclusion model来组织模板代码。
另外一个关于置入式模型的考虑是,与内联函数(inline function)和宏(marco)明显不同的是,非内联函数模板function template并不在调用端被展开。每当它们被实例化一次,编译器便从头创建一份函数拷贝。这个过程是完全自动化的,编译器可能最终在不同的文件中创建同一个模板实例化的两份拷贝,这时连接器会报错。但理论上,不需要程序员操心,这应该是编译系统的事。
最后要指出的是,
本文中对于function template所谈的内容,也适用于类模板class template的成员函数和static 成员变量。当然也同样适用于成员函数模板member function templates。