尽管标准 C++ 没有这样的要求,但某些编译器要求所有函数模板都需要在使用它的每个翻译单元中可用。实际上,对于这些编译器,模板函数的主体必须在头文件中可用。重复一遍:这意味着这些编译器不允许在非头文件(例如 .cpp 文件)中定义它们。澄清一下,在 C++ 语言中,这意味着:
// ORIGINAL version of xyz.h
template <typename T>
struct xyz
{
xyz();
~xyz();
};
不会对 ctor 和 dtor 的这些定义感到满意:
// ORIGINAL version of xyz.cpp
#include "xyz.h"
template <typename T>
xyz<T>::xyz() {}
template <typename T>
xyz<T>::~xyz() {}
因为使用它:
// main.cpp
#include "xyz.h"
int main()
{
xyz<int> xyzint;
return 0;
}
会产生错误。例如,使用 Comeau C++,您将得到:
C:\export>como xyz.cpp main.cpp
C++'ing xyz.cpp...
Comeau C/C++ 4.3.4.1 (May 29 2004 23:08:11) for MS_WINDOWS_x86
Copyright 1988-2004 Comeau Computing. All rights reserved.
MODE:non-strict warnings microsoft C++
C++'ing main.cpp...
Comeau C/C++ 4.3.4.1 (May 29 2004 23:08:11) for MS_WINDOWS_x86
Copyright 1988-2004 Comeau Computing. All rights reserved.
MODE:non-strict warnings microsoft C++
main.obj : error LNK2001: unresolved external symbol xyz<T1>::~xyz<int>() [with T1=int]
main.obj : error LNK2019: unresolved external symbol xyz<T1>::xyz<int>() [with T1=int] referenced in function _main
aout.exe : fatal error LNK1120: 2 unresolved externals
因为 xyz.cpp 中没有使用 ctor 或 dtor,因此不需要从那里进行实例化。无论好坏,这就是模板的工作原理。
解决这个问题的一种方法是显式请求实例化xyz
,在这个例子中xyz<int>
。通过暴力方式,可以通过在 xyz.cpp 末尾添加以下行来将其添加到 xyz.cpp 中:
template xyz<int>;
其中要求(全部)xyz<int>
被实例化。但这有点错误,因为这意味着每次产生新的 xyz 类型时,都必须修改实现文件 xyz.cpp。避免该文件的侵入性较小的方法是创建另一个文件:
// xyztir.cpp
#include "xyz.cpp" // .cpp file!!!, not .h file!!
template xyz<int>;
这仍然有些痛苦,因为每次产生新的 xyz 时仍然需要手动干预。在一个重要的程序中,这可能是一个不合理的维护需求。
因此,解决这个问题的另一种方法是#include "xyz.cpp"
进入 xyz.h 的末尾:
// xyz.h
// ... previous content of xyz.h ...
#include "xyz.cpp"
当然,您可以将 xyz.cpp 的内容逐字带到(剪切并粘贴)到 xyz.h 的末尾,从而摆脱 xyz.cpp;这是文件组织的问题,最终预处理的结果将是相同的,因为 ctor 和 dtor 主体将位于标头中,因此会带入任何编译请求,因为这将使用相应的标头。不管怎样,这都会产生副作用,现在每个模板都在您的头文件中。它可能会减慢编译速度,并可能导致代码膨胀。处理后者的一种方法是将相关函数(在本例中为 ctor 和 dtor)声明为内联函数,因此这需要您在运行示例中修改 xyz.cpp。
顺便说一句,一些编译器还要求某些函数在类内部内联定义,而不是在类外部定义,因此对于这些编译器来说,上面的设置需要进一步调整。请注意,这是一个编译器问题,而不是标准 C++ 问题,因此并非所有编译器都需要此问题。例如,Comeau C++ 没有,也不应该。查看http://www.comeaucomputing.com/4.0/docs/userman/ati.html http://www.comeaucomputing.com/4.0/docs/userman/ati.html有关我们当前设置的详细信息。简而言之,Comeau C++ 支持许多模型,包括一种接近导出关键字的意图(作为扩展)的模型,甚至支持导出本身。
最后,请注意,C++ 导出关键字旨在缓解最初的问题。然而,Comeau C++ 是目前唯一公开支持导出的编译器。看http://www.comeaucomputing.com/4.0/docs/userman/export.html http://www.comeaucomputing.com/4.0/docs/userman/export.html and http://www.comeaucomputing.com/4.3.0/minor/win95+/43stuff.txt http://www.comeaucomputing.com/4.3.0/minor/win95+/43stuff.txt了解一些细节。希望随着其他编译器达到标准 C++ 的兼容性,这种情况将会改变。在上面的例子中,使用export意味着返回到产生链接器错误的原始代码,并进行更改:使用export关键字在xyz.h中声明模板:
// xyz.h
export
// ... ORIGINAL contents of xyz.h ...
xyz.cpp 中的 ctor 和 dtor 将仅通过#includeing xyz.h 导出,它已经这样做了。因此,在这种情况下,您不需要 xyztir.cpp,也不需要 xyz.cpp 末尾的实例化请求,并且不需要将 ctor 或 dtor 手动带入 xyz.h。使用前面显示的命令行,编译器可能会自动为您完成这一切。