模板的多文件编程
我们常将函数定义放在源文件中,将函数声明放在头文件中。编译是针对单个源文件的,只要有函数声明,编译器就能知道函数调用是否正确;而将函数调用和函数定义对应起来的过程,可以延迟到链接时期。正是有了链接器的存在,函数声明和函数定义的分离才得以实现。
初学者往往也会将模板的声明和定义分散到不同的文件中,但这种做法是不对的,应该将模板的声明和定义都放在头文件中。
原因:模板的实例化是由编译器完成的,而不是由链接器完成的,这可能会导致在链接期间找不到对应的实例。
模板的显式实例化
在调用函数或者创建对象时由编译器自动完成的实例化,不需要程序员引导,称为隐式实例化;我们也可以通过代码明确告诉编译器需要针对哪个类型进行实例化,这称为显式实例化。
函数模板的显式实例化
显式实例化的例子
template void Swap(double &a, double &b);
前面是一个template关键字(不带< >),后面是一个普通的函数原型,组合在一起的意思是:将模板实例化成和函数原型对应的一个具体版本。
也可在函数调用的源文件中再增加一条语句
extern template void Swap(double &a, double &b);
该语句在前面增加了extern
关键字,它的作用是明确地告诉编译器,该版本的函数实例在其他文件中,请在链接期间查找。不过这条语句是多余的,即使不写,编译器发现当前文件中没有对应的模板定义,也会自动去其他文件中查找。
通过显式实例化,可以把函数模板的声明和定义分散到不同的文件中。
例:func.cpp 源码:
//交换两个数的值
template<typename T> void Swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
//冒泡排序算法
void bubble_sort(int arr[], int n){
for(int i=0; i<n-1; i++){
bool isSorted = true;
for(int j=0; j<n-1-i; j++){
if(arr[j] > arr[j+1]){
isSorted = false;
Swap(arr[j], arr[j+1]); //调用Swap()函数
}
}
if(isSorted) break;
}
}
template void Swap(double &a, double &b); //显式实例化定义
func.h 源码:
#ifndef _FUNC_H
#define _FUNC_H
template<typename T> void Swap(T &a, T &b);
void bubble_sort(int arr[], int n);
#endif
main.cpp 源码:
#include <iostream>
#include "func.h"
using namespace std;
//显示实例化声明(也可以不写)
extern template void Swap(double &a, double &b);
extern template void Swap(int &a, int &b);
int main(){
int n1 = 10, n2 = 20;
Swap(n1, n2);
double f1 = 23.8, f2 = 92.6;
Swap(f1, f2);
return 0;
}
类模板的显式实例化
针对char*
类型的显式实例化(定义形式)代码为:
template class Point<char*, char*>;
它的声明形式:
extern template class Point<char*, char*>;
不管是声明还是定义,都要带上class关键字,以表明这是针对类模板的
显式实例化一个类模板时,会一次性实例化该类的所有成员,包括成员变量和成员函数。
例:point.cpp:
#include <iostream>
#include "point.h"
using namespace std;
template<class T1, class T2>
void Point<T1, T2>::display() const{
cout<<"x="<<m_x<<", y="<<m_y<<endl;
}
//显式实例化定义
template class Point<char*, char*>;
template class Point<int, int>;
point.h:
#ifndef _POINT_H
#define _POINT_H
template<class T1, class T2>
class Point{
public:
Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
T1 getX() const{ return m_x; }
void setX(T1 x){ m_x = x; }
T2 getY() const{ return m_y; };
void setY(T2 y){ m_y = y; };
void display() const;
private:
T1 m_x;
T2 m_y;
};
#endif
main.cpp
#include <iostream>
#include "point.h"
using namespace std;
//显式实例化声明(也可以不写)
extern template class Point<char*, char*>;
extern template class Point<int, int>;
int main(){
Point<int, int> p1(10, 20);
p1.setX(40);
p1.setY(50);
cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;
Point<char*, char*> p2("东京180度", "北纬210度");
p2.display();
return 0;
}
显式实例化的缺陷
显式实例化必须要在模板的定义文件(实现文件)中对所有使用到的类型进行实例化。一个模板可能会在多个文件中使用到,要保持这些文件的同步更新是非常困难的。而对于库的开发者来说,他不能提前假设用户会使用哪些类型,所以根本就无法使用显式实例化,只能将模板的声明和定义(实现)全部放到头文件中;C++ 标准库几乎都是用模板来实现的,这些模板的代码也都位于头文件中。