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

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

目前使用模板的唯一便携方式是使用内联函数在头文件中实现它们。

为什么是这样?

(澄清:头文件不是唯一的便携式解决scheme,但它们是最方便的便携式解决scheme。)

没有必要将实现放在头文件中,请参阅本答案末尾的替代解决scheme。

无论如何,你的代码失败的原因是,当实例化一个模板时,编译器用给定的模板参数创build一个新的类。 例如:

template<typename T> struct Foo { T bar; void doSomething(T param) {/* do stuff using T */} }; // somewhere in a .cpp Foo<int> f; 

在阅读这一行时,编译器会创build一个新的类(我们称之为FooInt ),它相当于以下内容:

 struct FooInt { int bar; void doSomething(int param) {/* do stuff using int */} } 

因此,编译器需要访问方法的实现,用模板参数(在本例中为int )实例化它们。 如果这些实现不在头部,那么它们将不可访问,因此编译器将无法实例化模板。

一个常见的解决scheme是将模板声明写入头文件,然后在实现文件(例如.tpp)中实现该类,并将该实现文件包含在头的末尾。

 // Foo.h template <typename T> struct Foo { void doSomething(T param); }; #include "Foo.tpp" // Foo.tpp template <typename T> void Foo<T>::doSomething(T param) { //implementation } 

这样,实现仍然与声明分开,但编译器可以访问。

另一个解决scheme是保持实现分离,并显式实例化所有您需要的模板实例:

 // Foo.h // no implementation template <typename T> struct Foo { ... }; //---------------------------------------- // Foo.cpp // implementation of Foo's methods // explicit instantiations template class Foo<int>; template class Foo<float>; // You will only be able to use Foo with int or float 

如果我的解释不够清楚,可以查看关于这个主题的C ++ Super-FAQ 。

很多正确的答案在这里,但我想补充(完整性):

如果你在实现cpp文件的底部做了模板将被使用的所有types的显式实例化,链接器将能够像往常一样find它们。

编辑:添加显式模板实例的示例。 在定义模板之后使用,所有成员函数都已定义。

 template class vector<int>; 

这将实例化(从而使链接器可用)该类及其所有成员函数(仅)。 类似的语法适用于模板函数,所以如果你有非成员运算符重载,你可能需要做同样的事情。

上面的例子是相当无用的,因为除了常用的包含文件(预编译头文件?)使用extern template class vector<int> ,以防止其在所有其他文件(1000?)中实例化之外,vector在头文件中被完全定义那使用vector。

这是因为需要单独编译,因为模板是实例化风格的多态。

让我们接近具体的解释。 说我有以下文件:

  • foo.h中
    • 声明class MyClass<T>的接口
  • Foo.cpp中
    • 定义class MyClass<T>
  • bar.cpp
    • 使用MyClass<int>

单独的编译意味着我应该能够独立于bar.cpp编译foo.cpp 。 编译器完全独立完成每个编译单元的分析,优化和代码生成工作, 我们不需要做全程序分析。 只有链接器需要一次处理整个程序,而且链接器的工作要容易得多。

当我编译foo.cppbar.cpp甚至不需要存在,但我仍然应该能够链接foo.o,我已经有了bar.o我只生产了,而不需要重新编译foo .cppfoo.cpp甚至可以被编译成一个dynamic库,在没有foo.cpp的地方被分配到其他地方,并且和我们在写foo.cpp后写的代码链接起来。

“实例化风格的多态”意味着模板MyClass<T>并不是一个真正的generics类,可以被编译为可以用于任何T值的代码。 这将增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等.C ++模板的目的是避免编写几乎相同的class MyClass_intclass MyClass_float等,但仍然能够以编译的代码大部分就像我们分别编写了每个版本一样。 所以一个模板实际上就是一个模板。 一个类模板不是一个类,它是为我们遇到的每个T创build一个新类的一个配方。 模板不能被编译成代码,只有实例化模板的结果才能被编译。

所以当编译foo.cpp时,编译器不能看到bar.cpp知道需要MyClass<int> 。 它可以看到模板MyClass<T> ,但它不能发出代码(它是一个模板,而不是一个类)。 当编译bar.cpp时,编译器可以看到它需要创build一个MyClass<int> ,但是它看不到模板MyClass<T> (只有它在foo.h中的接口),所以它不能创build它。

如果foo.cpp本身使用MyClass<int> ,那么在编译foo.cpp时就会生成代码,所以当bar.o连接到foo.o时,它们可以被连接起来并工作。 我们可以使用这个事实来通过编写一个模板来在.cpp文件中实现一个有限的模板实例集。 但bar.cpp没有办法使用模板作为模板,并将其实例化为任何喜欢的types; 它只能使用foo.cpp的作者认为提供的模板类的预先存在的版本。

你可能会认为在编译模板时编译器应该“生成所有版本”,而在链接过程中从未使用的模板会被过滤掉。 除了巨大的开销和极端的困难,这样的方法将面临,因为“types修饰符”function,如指针和数组,甚至允许内置types产生无限数量的types,当我现在扩展我的程序通过增加:

  • baz.cpp
    • 声明和实现class BazPrivate ,并使用MyClass<BazPrivate>

除非我们做到这一点,否则这种方法是行不通的

  1. 每次更改程序中的任何其他文件时,都必须重新编译foo.cpp ,以防添加MyClass<T>的新实例。
  2. 要求baz.cpp包含(可能通过头文件包含) MyClass<T>的完整模板,以便编译器在编译baz.cpp期间可以生成MyClass<BazPrivate>

没有人喜欢(1),因为整个程序分析编译系统需要永远编译,并且因为它不可能在没有源代码的情况下分发已编译的库。 所以我们有(2)。

模板在实际编译成目标代码之前需要由编译器实例化 。 这个实例化只有在模板参数已知的情况下才能实现。 现在想象一下在ah中定义了模板函数的情况,在a.cpp定义并在b.cpp 。 当编译a.cpp ,不一定知道即将到来的编译b.cpp将需要模板的一个实例,更不用说那个特定的实例了。 对于更多的头文件和源文件,情况可能很快变得更加复杂。

人们可以争辩说,编译器可以变得更聪明,以“向前看”模板的所有用途,但我相信,创buildrecursion或其他复杂的场景并不困难。 AFAIK,编译器不会做这样的outlook。 正如Anton所指出的那样,一些编译器支持模板实例化的明确的导出声明,但并不是所有的编译器都支持它(还有?)。

实际上,C ++标准定义了“export”关键字,它可以简单地在头文件中声明模板并在其他地方实现它们。

不幸的是,没有stream行的编译器实现这个关键字。 我唯一知道的就是由Comeau C ++编译器使用的Edison Design Group编写的前端。 所有其他人都不得不在头文件中编写模板,因为编译器需要定义适当的实例化代码(正如其他人已经指出的那样)。

尽pipe标准的C ++没有这样的要求,但有些编译器要求所有的函数和类模板都需要在每个翻译单元中都可用。 实际上,对于这些编译器,模板函数的主体必须在头文件中可用。 重复一遍:这意味着这些编译器将不允许在非头文件(如.cpp文件)中定义它们

有一个导出关键字可以缓解这个问题,但是它并不是很便于携带。

模板必须用在头文件中,因为编译器需要实例化不同版本的代码,具体取决于模板参数的给定/推导的参数。 请记住,模板不直接表示代码,而是代表多个版本的模板。 在.cpp文件中编译非模板函数时,您正在编译一个具体的函数/类。 模板不是这种情况,可以用不同的types实例化,也就是在用具体的typesreplace模板参数时必须发出具体的代码。

export关键字的一个特点是用于单独编译。 exportfunction在C++11是不推荐的,AFAIK只有一个编译器实现它。 你不应该利用export 。 单独的编译在C++C++11是不可能的,但也许在C++17 ,如果有概念编译,我们可以有一些单独的编译方式。

为了实现单独的编译,必须单独进行模板主体检查。 看来,解决scheme是可能的概念。 看看最近在标准委员会上提出的这个文件 。 我认为这不是唯一的要求,因为你仍然需要在用户代码中实例化模板代码的代码。

模板的单独编译问题我想这也是迁移到模块,这是目前正在工作中出现的问题。

这意味着定义模板类的方法实现的最可移植的方法是在模板类定义中定义它们。

 template < typename ... > class MyClass { int myMethod() { // Not just declaration. Add method implementation here } }; 

这是完全正确的,因为编译器必须知道它是什么types的分配。 所以模板类,函数,枚举等也必须在头文件中实现,如果它是公开的或者是库的一部分(静态的或者dynamic的),因为头文件不会被编译成不像c / cpp文件是。 如果编译器不知道types是不能编译的。 在.NET中它可以是因为所有的对象都是从Object类派生的。 这不是.Net。

尽pipe上面有很多很好的解释,但是我错过了将模板分隔为标题和正文的实用方法。
我主要关心的是避免重新编译所有的模板用户,当我改变它的定义。
让模板实体中的所有模板实例化对我来说都不是一个可行的解决scheme,因为如果模板作者的使用和模板用户可能没有修改它的权利,模板作者可能不知道。
我采取了以下方法,这也适用于较旧的编译器(gcc 4.3.4,aCC A.03.13)。

对于每个模板用法,在它自己的头文件中有一个typedef(由UML模型生成)。 它的主体包含实例化(最后在一个链接在库中的库)。
模板的每个用户都包含该头文件并使用typedef。

示意图:

MyTemplate.h:

 #ifndef MyTemplate_h #define MyTemplate_h 1 template <class T> class MyTemplate { public: MyTemplate(const T& rt); void dump(); T t; }; #endif 

MyTemplate.cpp:

 #include "MyTemplate.h" #include <iostream> template <class T> MyTemplate<T>::MyTemplate(const T& rt) : t(rt) { } template <class T> void MyTemplate<T>::dump() { cerr << t << endl; } 

MyInstantiatedTemplate.h:

 #ifndef MyInstantiatedTemplate_h #define MyInstantiatedTemplate_h 1 #include "MyTemplate.h" typedef MyTemplate< int > MyInstantiatedTemplate; #endif 

MyInstantiatedTemplate.cpp:

 #include "MyTemplate.cpp" template class MyTemplate< int >; 

main.cpp中:

 #include "MyInstantiatedTemplate.h" int main() { MyInstantiatedTemplate m(100); m.dump(); return 0; } 

这样只有模板实例化需要重新编译,而不是所有的模板用户(和依赖)。

如果担心的是将.h作为所有使用它的.cpp模块的一部分进行编译所产生的额外的编译时间和二进制大小膨胀,那么在许多情况下,您可以做的是使模板类从非模板化的基类inheritance接口的非types依赖部分,并且该基类可以在.cpp文件中实现。

在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。 在编译和链接过程中,.cpp文件被转换为纯粹的对象或机器代码,其中包含引用或未定义的符号,因为包含在main.cpp中的.h文件没有实现YET。 这些已准备好与另一个定义模板实现的目标文件相链接,因此您可以使用完整的a.out可执行文件。 然而,由于模板需要在编译步骤中进行处理,以便为您在主程序中执行的每个模板实例化生成代码,因此将main.cpp编译为main.o,然后编译模板.cpp到template.o,然后链接将无法实现模板的目的,因为我链接不同的模板实例化到相同的模板实现! 和模板应该做相反的,即有一个实现,但允许通过使用一个类的许多可用的实例化。

意义typename T在编译步骤中replacetypename T get而不是链接步骤,所以如果我试图编译一个模板而不将T作为一个具体的值typesreplace,所以它不会工作,因为这是模板的定义它是一个编译时过程,元编程就是使用这个定义。

有一个独立的实现方法如下。

 //inner_foo.h template <typename T> struct Foo { void doSomething(T param); }; //foo.tpp #include "inner_foo.h" template <typename T> void Foo<T>::doSomething(T param) { //implementation } //foo.h #include <foo.tpp> //main.cpp #include <foo.h> 

inner_foo具有前向声明。 foo.tpp具有实现并包含inner_foo.h; 和foo.h将只有一行,包括foo.tpp。

在编译时,将foo.h的内容复制到foo.tpp,然后将整个文件复制到foo.h中,然后编译。 这样就没有限制,命名是一致的,换来一个额外的文件。

我这样做是因为代码的静态分析器在* .tpp中看不到类的前向声明时会中断。 在任何IDE中编写代码或使用YouCompleteMe或其他代码时,这是很烦人的。