外部模板
外部模板是为了减少由同一模板实例化出多个同类型函数的冗余而出现的。我们先通过一段代码来了解,一个模板实例化出多个同类型函数,产生代码冗余的情况,具体内容如下所示:
//head.h
template <typename T> //在头文件中定义函数模板
void func(T x)
{
cout << x << endl;
}
//1.cpp //在1.cpp中实例化模板函数
#include “head.h”
template void func<int> (int); //显式实例化为带有int参数的函数
void call_func_1()
{
func(1); //调用func()函数
}
//2.cpp //在2.cpp中实例化模板函数
#include “head.h”
template void func<int> (int); //显式实例化为带有int参数的函数
void call_func_2()
{
func(2); //调用func()函数
}
上述代码中,1.cpp和2.cpp文件都将func()完成了实例化,而且都实例化为带有int型参数的函数。对于编译器来说,源代码中出现的每一处模板实例化,都需要去做实例化的工作,生成对应的函数代码,而在链接时,链接器还需要移除重复的实例化代码。显然,这样会产生大量的冗余代码,也降低了编译效率。
对于这种情况,C++11新标准通过外部模板的声明,解决了生成重复代码的情况。声明外部模板的语法就是在显式实例化的形式前面加了extern关键字,具体形式如下所示:
extern template void func<int> (int);
使用外部模板,将上面的代码稍作修改,内容如下所示:
//head.h
template <typename T> //在头文件中定义函数模板
void func(T x){ cout << x << endl; }
//1.cpp //在1.cpp中实例化模板函数
#include “head.h”
template void func<int> (int); //显式实例化为带有int参数的函数
void call_func_1(void) { func(1); }
//2.cpp //在2.cpp中声明外部模板
#include “head.h”
extern template void func<int> (int); //声明外部模板
void call_func_2(void){ func(2); }
上述代码中,在2.cpp中声明外部模板,这样2.cpp生成目标文件时不会编译出func()函数的实际代码。编译的简要过程如图1所示。
图1 使用外部模板后的编译过程
由于目标文件2.o中不再包含func()函数的的实例代码,编译和链接工作将会更高效。使用外部模板与声明外部变量很类似,都是使用同一份内容,只是在需要使用共享内容的时候进行声明。
与声明外部变量类似,还可以将外部模板的声明放在头文件中,这样所有需要使用模板函数的模块只需要包含头文件就可以了。
使用外部模板时,还需要注意以下问题:
● 如果外部模板声明出现于某个编译单元中,那么与之对应的显式实例化必须出现于另一个编译单元中或者同一个编译单元的后续代码中。
● 外部模板声明不能用于声明一个静态函数,但可以声明类内静态成员函数,因为静态函数不具有外部链接属性,不能在本编译单元外使用。
将上面的代码稍作修改,外部模板声明放在头文件中,代码如下:
//head.h
#ifndef HEAD_H
#define HEAD_H
#include <iostream>
using namespace std;
template <typename T> //在头文件中定义函数模板
void func(T x)
{
cout << x << endl;
}
extern template void func<int>(int); //声明外部模板
void call_func_1();
void call_func_2();
#endif
//1.cpp
#include "head.h"
template void func<int>(int); //显式实例化
void call_func_1(){ func(1); }
//2.cpp
#include "head.h"
void call_func_2(){ func(2); }
上述代码中,在头文件中声明了外部模板,则包含它的1.cpp、2.cpp文件都会有该声明。1.cpp中使用显式实例化,实例化出了带有int型参数的函数,在2.cpp中只需要有外部模板声明,不需要再进行显式实例化。
C++11中模板的显式实例化和外部模板声明,与全局变量的定义、外部声明和使用很类似,不同之处在于,就是不使用外部模板声明也不会有任何问题,只是多了一些冗余代码,降低了编译和链接效率,因此通常将外部模板声明作为针对编译器的优化手段使用。对于大型项目而言,应该充分考虑这种优化,但在通常情况下也无需过分担心这种开销。