当我们组织模板代码时,对于模板函数,把模板函数的声明放在.h文件,模板函数的定义放在.cpp文件;对于模板类,将模板类的定义放在.h文件,成员函数的定义放在.cpp文件,编译器编译时会报错。
// xx.h
template<typename T>
inline T const& max(T const& a, T const& b); // 模板函数的声明
template<typename T>
class MyClass // 模板类的定义
{
public:
void Foo(); // 成员函数的声明
}
// xx.cpp
template<typename T>
inline T const& max(T const& a, T const& b) // 模板函数的定义
{
return a < b ? b : a;
}
template<typename T>
void MyClass<T>::Foo() // 成员函数的定义
{
}
// main.cpp
#include<xx.h>
int main()
{
max(2, 3); // ERROR:无法解析外部符号max<int>()
MyClass<int> myClass;
myClass.Foo(); // ERROR:无法解析外部符号MyClass<int>::Foo()
return 0;
}
首先,这个报错发生在链接阶段。在编译阶段,调用max()和MyClass::Foo()时,编译器只看到了它们的声明,但编译依然会通过。编译器会生成一个指向定义的引用,让链接器在链接的时候将引用指向定义。但是cpp文件作为一个编译单元是互相独立的,就是xx.cpp文件编译时,编译器不知道main.cpp文件中对max()和MyClass::Foo()的调用,也就不会实例化Foo()和MyClass,也就不会生成max()和MyClass::Foo()的定义,这样链接器链接的时候就找不到定义。
包含模型是对上面问题的一个解决方案,将模板的声明和定义都放在.h文件。c++自带的头文件和STL就是采用的这种方式。
// xx.h
template<typename T>
inline T const& max(T const& a, T const& b); // 模板函数的声明
template<typename T>
void MyClass<T>::Foo() // 成员函数的定义
{
}
template<typename T>
class MyClass; // 模板类的声明
template<typename T>
class MyClass // 模板类的定义
{
public:
void Foo(){} // 成员函数的声明和定义
void Print(); // 成员函数的声明
}
template<typename T> // 成员函数的定义
void Print()
{
}
// 类的成员函数默认都是内联的,如果将成员函数的定义放到.cpp文件就不是内联的了。
如果有多个cpp文件包含xx.h,如果每个cpp文件都会对xx.h中的模板进行max()的实例化,这样max()不是有多个定义了吗?不会产生多个定义,c++的编译系统会解决这个问题。但是特别是当包含c++的头文件时(比如等),由于模板,编译器会产生大量的模板实例代码,很大的增加了编译的复杂程度,使编译的时间也大量增加。对于这个问题也有解决方案:
- 分离模型,并不是所有的编译器都支持
- 预编译头文件