为什么模板只能在头文件中实现?

引自C ++标准库:教程和手册

目前使用模板的唯一可移植方法是使用内联函数在头文件中实现它们。

为什么是这样?


墨色风雨
浏览 1567回答 7
7回答

一只萌萌小番薯

它是不是需要把执行的头文件,看到这个答案的末尾替代解决方案。无论如何,代码失败的原因是,在实例化模板时,编译器会创建一个具有给定模板参数的新类。例如:template<typename&nbsp;T>struct&nbsp;Foo{ &nbsp;&nbsp;&nbsp;&nbsp;T&nbsp;bar; &nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;doSomething(T&nbsp;param)&nbsp;{/*&nbsp;do&nbsp;stuff&nbsp;using&nbsp;T&nbsp;*/}};//&nbsp;somewhere&nbsp;in&nbsp;a&nbsp;.cppFoo<int>&nbsp;f;读取此行时,编译器将创建一个新类(让我们调用它FooInt),这相当于以下内容:struct&nbsp;FooInt{ &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;bar; &nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;doSomething(int&nbsp;param)&nbsp;{/*&nbsp;do&nbsp;stuff&nbsp;using&nbsp;int&nbsp;*/}}因此,编译器需要访问方法的实现,以使用模板参数(在本例中int)实例化它们。如果这些实现不在标头中,则它们将不可访问,因此编译器将无法实例化模板。一个常见的解决方案是在头文件中编写模板声明,然后在实现文件(例如.tpp)中实现该类,并在头的末尾包含此实现文件。//&nbsp;Foo.htemplate&nbsp;<typename&nbsp;T>struct&nbsp;Foo{ &nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;doSomething(T&nbsp;param);};#include&nbsp;"Foo.tpp"//&nbsp;Foo.tpptemplate&nbsp;<typename&nbsp;T>void&nbsp;Foo<T>::doSomething(T&nbsp;param){ &nbsp;&nbsp;&nbsp;&nbsp;//implementation}这样,实现仍然与声明分离,但编译器可以访问。另一种解决方案是保持实现分离,并显式实例化您需要的所有模板实例://&nbsp;Foo.h//&nbsp;no&nbsp;implementationtemplate&nbsp;<typename&nbsp;T>&nbsp;struct&nbsp;Foo&nbsp;{&nbsp;...&nbsp;};//----------------------------------------&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;Foo.cpp//&nbsp;implementation&nbsp;of&nbsp;Foo's&nbsp;methods//&nbsp;explicit&nbsp;instantiationstemplate&nbsp;class&nbsp;Foo<int>;template&nbsp;class&nbsp;Foo<float>; //&nbsp;You&nbsp;will&nbsp;only&nbsp;be&nbsp;able&nbsp;to&nbsp;use&nbsp;Foo&nbsp;with&nbsp;int&nbsp;or&nbsp;float如果我的解释不够清楚,你可以看一下关于这个主题的C ++ Super-FAQ。

米琪卡哇伊

这里有很多正确答案,但我想补充一下(为了完整性):如果您在实现cpp文件的底部对模板将使用的所有类型进行显式实例化,则链接器将能够像往常一样找到它们。编辑:添加显式模板实例化的示例。在定义模板后使用,并且已定义所有成员函数。template&nbsp;class&nbsp;vector<int>;这将实例化(并因此使链接器可用)类及其所有成员函数(仅)。类似的语法适用于模板函数,因此如果您有非成员运算符重载,则可能需要对它们执行相同的操作。上面的例子是相当无用的,因为vector是在头文件中完全定义的,除非公共包含文件(预编译头文件?)使用extern template class vector<int>它以防止它在使用vector的所有其他(1000?)文件中实例化它。

MM们

在将模板实际编译为目标代码之前,模板需要由编译器实例化。只有在模板参数已知的情况下才能实现此实例化。现在想象一下模板函数在其中声明a.h,定义a.cpp和使用的场景b.cpp。在a.cpp编译时,不一定知道即将进行的编译b.cpp将需要模板的实例,更不用说具体的实例。对于更多的头文件和源文件,情况可能会变得更加复杂。有人可以说,编译器可以变得更聪明,可以“预见”模板的所有用途,但我确信创建递归或其他复杂场景并不困难。AFAIK,编译器不会这样做。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(但是?)。

倚天杖

虽然标准C ++没有这样的要求,但是一些编译器要求所有函数和类模板都需要在它们使用的每个转换单元中可用。实际上,对于那些编译器,模板函数的主体必须在头文件中可用。重复:这意味着那些编译器不允许在非头文件中定义它们,例如.cpp文件有一个导出关键字可以缓解这个问题,但它远不是可移植的。
打开App,查看更多内容
随时随地看视频慕课网APP