为什么模板只能在头文件中实现?
Quote from The C++ standard library: a tutorial and handbook:
The only portable way of using templates at the moment is to implement them in header files by using inline functions.
Why is this?
(Clarification: header files are not the only portable solution. But they are the most convenient portable solution.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(20)
警告:没有必要将实现放入头文件中,请参阅本答案末尾的替代解决方案。
无论如何,您的代码失败的原因是,在实例化模板时,编译器使用给定的模板参数创建一个新类。例如:
当读取这一行时,编译器将创建一个新类(我们称之为 FooInt ),这相当于以下内容:
因此,编译器需要有权访问方法的实现,使用模板参数(在本例中为
int
)实例化它们。如果这些实现不在标头中,则它们将不可访问,因此编译器将无法实例化模板。对此的常见解决方案是将模板声明写入头文件中,然后在实现文件(例如 .tpp)中实现该类,并将该实现文件包含在头文件的末尾。
Foo.h
Foo.tpp
这样,实现仍然与声明分离,但可供编译器访问。
替代解决方案
另一个解决方案是保持实现分离,并显式实例化您需要的所有模板实例:
Foo.h
Foo.cpp
如果我的解释不够清楚,您可以查看 关于此主题的 C++ 超级常见问题解答。
Caveat: It is not necessary to put the implementation in the header file, see the alternative solution at the end of this answer.
Anyway, the reason your code is failing is that, when instantiating a template, the compiler creates a new class with the given template argument. For example:
When reading this line, the compiler will create a new class (let's call it
FooInt
), which is equivalent to the following:Consequently, the compiler needs to have access to the implementation of the methods, to instantiate them with the template argument (in this case
int
). If these implementations were not in the header, they wouldn't be accessible, and therefore the compiler wouldn't be able to instantiate the template.A common solution to this is to write the template declaration in a header file, then implement the class in an implementation file (for example .tpp), and include this implementation file at the end of the header.
Foo.h
Foo.tpp
This way, implementation is still separated from declaration, but is accessible to the compiler.
Alternative solution
Another solution is to keep the implementation separated, and explicitly instantiate all the template instances you'll need:
Foo.h
Foo.cpp
If my explanation isn't clear enough, you can have a look at the C++ Super-FAQ on this subject.
这是因为需要单独编译,并且模板是实例化风格的多态性。
让我们更接近具体的解释。假设我有以下文件:
class MyClass
的接口class MyClass
的实现MyClass
单独编译意味着我应该能够独立于 bar.cpp 编译 foo.cpp。编译器完全独立地在每个编译单元上完成分析、优化和代码生成的所有艰苦工作;我们不需要进行整个程序分析。只有链接器需要立即处理整个程序,并且链接器的工作要容易得多。
当我编译 foo.cpp 时,bar.cpp 甚至不需要存在,但我仍然应该能够链接 foo.o我已经和我刚刚制作的bar.o一起使用了,不需要重新编译foo.cpp。 foo.cpp 甚至可以编译成动态库,在没有 foo.cpp 的情况下分发到其他地方,并与他们在我编写 foo.cpp 多年后编写的代码链接。
“实例化样式多态性”意味着模板
MyClass
实际上并不是一个可以编译为适用于任何T
值的代码的泛型类。这会增加开销,例如装箱、需要将函数指针传递给分配器和构造函数等。C++ 模板的目的是避免编写几乎相同的class MyClass_int
、class MyClass_float
code> 等,但仍然能够最终得到编译后的代码,就像我们单独编写每个版本一样。因此,模板实际上就是一个模板;类模板不是类,它是为我们遇到的每个T
创建新类的方法。模板不能编译成代码,只能编译实例化模板的结果。所以当编译foo.cpp时,编译器看不到bar.cpp来知道需要
MyClass
。它可以看到模板MyClass
,但它无法为此发出代码(它是模板,而不是类)。而当bar.cpp编译时,编译器可以看到它需要创建一个MyClass
,但是却看不到模板MyClass<
。 T> (仅其在 foo.h 中的接口),因此无法创建它。如果 foo.cpp 本身使用
MyClass
,则在编译 foo.cpp 时将生成该代码,因此当 bar.o 链接到 foo.o,它们可以连接起来并且可以工作。我们可以利用这一事实,通过编写单个模板来允许在 .cpp 文件中实现一组有限的模板实例化。但是 bar.cpp 无法使用模板作为模板并在它喜欢的任何类型上实例化它;它只能使用 foo.cpp 作者认为提供的模板类的现有版本。您可能认为在编译模板时,编译器应该“生成所有版本”,而在链接过程中过滤掉从未使用过的版本。除了巨大的开销和这种方法将面临的极端困难之外,因为指针和数组等“类型修饰符”功能甚至允许内置类型产生无限数量的类型,当我现在扩展我的程序时会发生什么通过添加:
类 BazPrivate
,并使用MyClass
这不可能起作用,除非我们
MyClass
的新实例MyClass
的完整模板,以便编译器可以生成MyClass
在编译baz.cpp期间。没有人喜欢(1),因为整个程序分析编译系统需要永远来编译,并且因为它使得在没有源代码的情况下无法分发已编译的库。所以我们有(2)。
It's because of the requirement for separate compilation and because templates are instantiation-style polymorphism.
Lets get a little closer to concrete for an explanation. Say I've got the following files:
class MyClass<T>
class MyClass<T>
MyClass<int>
Separate compilation means I should be able to compile foo.cpp independently from bar.cpp. The compiler does all the hard work of analysis, optimization, and code generation on each compilation unit completely independently; we don't need to do whole-program analysis. It's only the linker that needs to handle the entire program at once, and the linker's job is substantially easier.
bar.cpp doesn't even need to exist when I compile foo.cpp, but I should still be able to link the foo.o I already had together with the bar.o I've only just produced, without needing to recompile foo.cpp. foo.cpp could even be compiled into a dynamic library, distributed somewhere else without foo.cpp, and linked with code they write years after I wrote foo.cpp.
"Instantiation-style polymorphism" means that the template
MyClass<T>
isn't really a generic class that can be compiled to code that can work for any value ofT
. That would add overhead such as boxing, needing to pass function pointers to allocators and constructors, etc. The intention of C++ templates is to avoid having to write nearly identicalclass MyClass_int
,class MyClass_float
, etc, but to still be able to end up with compiled code that is mostly as if we had written each version separately. So a template is literally a template; a class template is not a class, it's a recipe for creating a new class for eachT
we encounter. A template cannot be compiled into code, only the result of instantiating the template can be compiled.So when foo.cpp is compiled, the compiler can't see bar.cpp to know that
MyClass<int>
is needed. It can see the templateMyClass<T>
, but it can't emit code for that (it's a template, not a class). And when bar.cpp is compiled, the compiler can see that it needs to create aMyClass<int>
, but it can't see the templateMyClass<T>
(only its interface in foo.h) so it can't create it.If foo.cpp itself uses
MyClass<int>
, then code for that will be generated while compiling foo.cpp, so when bar.o is linked to foo.o they can be hooked up and will work. We can use that fact to allow a finite set of template instantiations to be implemented in a .cpp file by writing a single template. But there's no way for bar.cpp to use the template as a template and instantiate it on whatever types it likes; it can only use pre-existing versions of the templated class that the author of foo.cpp thought to provide.You might think that when compiling a template the compiler should "generate all versions", with the ones that are never used being filtered out during linking. Aside from the huge overhead and the extreme difficulties such an approach would face because "type modifier" features like pointers and arrays allow even just the built-in types to give rise to an infinite number of types, what happens when I now extend my program by adding:
class BazPrivate
, and usesMyClass<BazPrivate>
There is no possible way that this could work unless we either
MyClass<T>
MyClass<T>
, so that the compiler can generateMyClass<BazPrivate>
during compilation of baz.cpp.Nobody likes (1), because whole-program-analysis compilation systems take forever to compile , and because it makes it impossible to distribute compiled libraries without the source code. So we have (2) instead.
这里有很多正确的答案,但我想添加这个(为了完整性):
如果您在实现 cpp 文件的底部,对模板将使用的所有类型进行显式实例化,链接器将能够找到它们照常。
编辑:添加显式模板实例化的示例。在定义模板并且定义所有成员函数后使用。
这将实例化(并因此可供链接器使用)类及其所有成员函数(仅)。类似的语法适用于函数模板,因此如果您有非成员运算符重载,您可能需要对它们执行相同的操作。
上面的例子是相当无用的,因为向量是在标头中完全定义的,除非公共包含文件(预编译标头?)使用外部模板类向量以防止它在所有情况下实例化。使用向量的其他(1000?)文件。
Plenty correct answers here, but I wanted to add this (for completeness):
If you, at the bottom of the implementation cpp file, do explicit instantiation of all the types the template will be used with, the linker will be able to find them as usual.
Edit: Adding example of explicit template instantiation. Used after the template has been defined, and all member functions has been defined.
This will instantiate (and thus make available to the linker) the class and all its member functions (only). Similar syntax works for function templates, so if you have non-member operator overloads you may need to do the same for those.
The above example is fairly useless since vector is fully defined in headers, except when a common include file (precompiled header?) uses
extern template class vector<int>
so as to keep it from instantiating it in all the other (1000?) files that use vector.在实际将模板编译为目标代码之前,需要由编译器实例化模板。仅当模板参数已知时才能实现此实例化。现在想象一个场景,模板函数在
ah
中声明,在a.cpp
中定义并在b.cpp
中使用。编译a.cpp
时,不一定知道即将进行的编译b.cpp
是否需要模板的实例,更不用说具体是哪个实例了。对于更多的头文件和源文件,情况很快就会变得更加复杂。有人可能会说,编译器可以变得更智能,可以“预见”模板的所有使用,但我确信创建递归或其他复杂的场景并不困难。 AFAIK,编译器不会进行此类前瞻性操作。正如 Anton 指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(还?)。
Templates need to be instantiated by the compiler before actually compiling them into object code. This instantiation can only be achieved if the template arguments are known. Now imagine a scenario where a template function is declared in
a.h
, defined ina.cpp
and used inb.cpp
. Whena.cpp
is compiled, it is not necessarily known that the upcoming compilationb.cpp
will require an instance of the template, let alone which specific instance would that be. For more header and source files, the situation can quickly get more complicated.One can argue that compilers can be made smarter to "look ahead" for all uses of the template, but I'm sure that it wouldn't be difficult to create recursive or otherwise complicated scenarios. AFAIK, compilers don't do such look aheads. As Anton pointed out, some compilers support explicit export declarations of template instantiations, but not all compilers support it (yet?).
实际上,在 C++11 之前,标准定义了
export
关键字,可以在头文件中声明模板并在其他地方实现它们。从某种意义上来说。事实并非如此,因为只有指出:流行的编译器都没有实现这个关键字。该功能的唯一实现是由 Edison Design Group 编写的前端,由 Comeau C++ 编译器使用。所有其他要求您在头文件中编写模板,因为编译器需要模板定义才能正确实例化(正如其他人已经指出的那样)。
因此,ISO C++ 标准委员会决定删除 C++11 模板的
导出
功能。Actually, prior to C++11 the standard defined the
export
keyword that would make it possible to declare templates in a header file and implement them elsewhere. In a manner of speaking. Not really, as the only ones who ever implemented that feature pointed out:None of the popular compilers implemented this keyword. The only implementation of the feature was in the frontend written by the Edison Design Group, which is used by the Comeau C++ compiler. All others required you to write templates in header files, because the compiler needs the template definition for proper instantiation (as others pointed out already).
As a result, the ISO C++ standard committee decided to remove the
export
feature of templates with C++11.尽管标准 C++ 没有这样的要求,但某些编译器要求所有函数和类模板都需要在它们使用的每个翻译单元中可用。实际上,对于这些编译器,模板函数的主体必须在头文件中可用。重复一遍:这意味着这些编译器不允许在非头文件(例如 .cpp 文件)中定义它们。
有一个 export 关键字应该可以缓解这个问题,但它与便于携带。
Although standard C++ has no such requirement, some compilers require that all function and class templates need to be made available in every translation unit they are used. In effect, for those compilers, the bodies of template functions must be made available in a header file. To repeat: that means those compilers won't allow them to be defined in non-header files such as .cpp files
There is an export keyword which is supposed to mitigate this problem, but it's nowhere close to being portable.
模板经常在头文件中使用,因为编译器需要实例化不同版本的代码,具体取决于为模板参数给出/推导的参数,并且(作为程序员)让编译器多次重新编译相同的代码并在以后进行重复数据删除会更容易。
请记住,模板并不直接代表代码,而是代表该代码的多个版本的模板。
当您在
.cpp
文件中编译非模板函数时,您正在编译一个具体的函数/类。模板则不然,模板可以用不同的类型实例化,即用具体类型替换模板参数时必须发出具体代码。
有一个带有
export
关键字的功能,旨在用于单独编译。export
功能在C++11
中已被弃用,并且据我所知,只有一个编译器实现了该功能。您不应该使用
导出
。单独编译在
C++
或C++11
中是不可能的,但在C++17
中可能是这样,如果概念允许的话,我们可以有某种单独编译的方式。为了实现单独编译,必须可以进行单独的模板主体检查。
看来用概念是可以解决的。
最近看一下这篇论文提出于
标准委员会会议。
我认为这不是唯一的要求,因为您仍然需要在用户代码中实例化模板代码的代码。
模板的单独编译问题我想这也是迁移到模块时出现的问题,目前正在处理中。
编辑:截至 2020 年 8 月,模块已经成为 C++ 的现实: https://en.cppreference .com/w/cpp/语言/模块
Templates are often used in headers because the compiler needs to instantiate different versions of the code, depending on the parameters given/deduced for template parameters, and it's easier (as a programmer) to let the compiler recompile the same code multiple times and deduplicate later.
Remember that a template doesn't represent code directly, but a template for several versions of that code.
When you compile a non-template function in a
.cpp
file, you are compiling a concrete function/class.This is not the case for templates, which can be instantiated with different types, namely, concrete code must be emitted when replacing template parameters with concrete types.
There was a feature with the
export
keyword that was meant to be used for separate compilation.The
export
feature is deprecated inC++11
and, AFAIK, only one compiler implemented it.You shouldn't make use of
export
.Separate compilation is not possible in
C++
orC++11
but maybe inC++17
, if concepts make it in, we could have some way of separate compilation.For separate compilation to be achieved, separate template body checking must be possible.
It seems that a solution is possible with concepts.
Take a look at this paper recently presented at the
standards committee meeting.
I think this is not the only requirement, since you still need to instantiate code for the template code in user code.
The separate compilation problem for templates I guess it's also a problem that is arising with the migration to modules, which is currently being worked.
EDIT: As of August 2020 Modules are already a reality for C++: https://en.cppreference.com/w/cpp/language/modules
尽管上面有很多很好的解释,但我缺少一种将模板分为标题和正文的实用方法。
我主要关心的是当我更改其定义时避免重新编译所有模板用户。
将所有模板实例化在模板主体中对我来说不是一个可行的解决方案,因为模板作者可能不知道其用法,并且模板用户可能无权修改它。
我采用了以下方法,该方法也适用于较旧的编译器(gcc 4.3.4、aCC A.03.13)。
对于每个模板的使用,其自己的头文件(从 UML 模型生成)中都有一个 typedef。它的主体包含实例化(最终位于最后链接的库中)。
模板的每个用户都包含该头文件并使用 typedef。
示意性示例:
MyTemplate.h:
MyTemplate.cpp:
MyInstantiatedTemplate.h:
MyInstantiatedTemplate.cpp:
main.cpp:
这样,只有模板实例需要重新编译,而不是所有模板用户(和依赖项)。
Even though there are plenty of good explanations above, I'm missing a practical way to separate templates into header and body.
My main concern is avoiding recompilation of all template users, when I change its definition.
Having all template instantiations in the template body is not a viable solution for me, since the template author may not know all if its usage and the template user may not have the right to modify it.
I took the following approach, which works also for older compilers (gcc 4.3.4, aCC A.03.13).
For each template usage there's a typedef in its own header file (generated from the UML model). Its body contains the instantiation (which ends up in a library which is linked in at the end).
Each user of the template includes that header file and uses the typedef.
A schematic example:
MyTemplate.h:
MyTemplate.cpp:
MyInstantiatedTemplate.h:
MyInstantiatedTemplate.cpp:
main.cpp:
This way only the template instantiations will need to be recompiled, not all template users (and dependencies).
这意味着定义模板类的方法实现的最可移植的方法是在模板类定义中定义它们。
It means that the most portable way to define method implementations of template classes is to define them inside the template class definition.
当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。
在编译和链接过程中,.cpp 文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为 main.cpp 中包含的 .h 文件尚未实现。这些已准备好与另一个定义模板实现的目标文件链接,因此您拥有完整的 a.out 可执行文件。
然而,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例化生成代码,因此简单地编译与其头文件分开的模板是行不通的,因为它们总是齐头并进,正是出于这个原因每个模板实例化实际上都是一个全新的类。在常规类中,您可以将 .h 和 .cpp 分开,因为 .h 是该类的蓝图,而 .cpp 是原始实现,因此可以定期编译和链接任何实现文件,但是使用模板 .h 是如何进行的蓝图类的外观不应该是对象的外观,这意味着模板 .cpp 文件不是类的原始常规实现,它只是类的蓝图,因此无法编译 .h 模板文件的任何实现,因为你需要需要具体编译的东西,模板在这个意义上是抽象的。
因此,模板永远不会单独编译,并且仅在其他源文件中具有具体实例的地方进行编译。但是,具体实例化需要知道模板文件的实现,因为简单地使用 .h 文件中的具体类型修改
typename T
并不能完成这项工作,因为 .cpp 在那里链接,我稍后找不到它,因为记住模板是抽象的并且无法编译,所以我现在被迫给出实现,这样我就知道要编译和链接什么,现在我有了实现它被链接到封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道该类在使用我提供的类型时应该是什么样子,我就不能这样做,除非我通知编译器模板实现,因此现在编译器可以用我的类型替换T
并创建一个可供编译和链接的具体类。总而言之,模板是类应该如何显示的蓝图,类是对象应该如何显示的蓝图。
我无法将模板与具体实例化分开编译,因为编译器只编译具体类型,换句话说,模板至少在 C++ 中是纯粹的语言抽象。可以这么说,我们必须对模板进行去抽象,为此我们需要给它们一个具体的类型来处理,以便我们的模板抽象可以转换为常规的类文件,进而可以正常编译。将模板.h 文件和模板.cpp 文件分开是没有意义的。这是荒谬的,因为 .cpp 和 .h 的分离只是 .cpp 可以单独编译和单独链接的地方,而模板则不能单独编译,因为模板是一个抽象,因此我们总是被迫将抽象始终与具体实例化放在一起,其中具体实例化始终必须了解所使用的类型。
这意味着
typename T
在编译步骤而不是链接步骤中被替换,因此如果我尝试编译模板而不将T
替换为具体值类型,这对编译器因此无法创建目标代码,因为它不知道T
是什么。从技术上讲,创建某种功能来保存 template.cpp 文件并在其他来源中找到类型时可以切换它们,我认为该标准确实有一个关键字
export
,它将允许您将模板放入单独的 cpp 文件中,但实际上并没有多少编译器实现此功能。附带说明一下,在对模板类进行专门化时,您可以将标头与实现分开,因为根据定义的专门化意味着我专门针对可以单独编译和链接的具体类型。
The compiler will generate code for each template instantiation when you use a template during the compilation step.
In the compilation and linking process .cpp files are converted to pure object or machine code which in them contains references or undefined symbols because the .h files that are included in your main.cpp have no implementation YET. These are ready to be linked with another object file that defines an implementation for your template and thus you have a full a.out executable.
However since templates need to be processed in the compilation step in order to generate code for each template instantiation that you define, so simply compiling a template separate from it's header file won't work because they always go hand and hand, for the very reason that each template instantiation is a whole new class literally. In a regular class you can separate .h and .cpp because .h is a blueprint of that class and the .cpp is the raw implementation so any implementation files can be compiled and linked regularly, however using templates .h is a blueprint of how the class should look not how the object should look meaning a template .cpp file isn't a raw regular implementation of a class, it's simply a blueprint for a class, so any implementation of a .h template file can't be compiled because you need something concrete to compile, templates are abstract in that sense.
Therefore templates are never separately compiled and are only compiled wherever you have a concrete instantiation in some other source file. However, the concrete instantiation needs to know the implementation of the template file, because simply modifying the
typename T
using a concrete type in the .h file is not going to do the job because what .cpp is there to link, I can't find it later on because remember templates are abstract and can't be compiled, so I'm forced to give the implementation right now so I know what to compile and link, and now that I have the implementation it gets linked into the enclosing source file. Basically, the moment I instantiate a template I need to create a whole new class, and I can't do that if I don't know how that class should look like when using the type I provide unless I make notice to the compiler of the template implementation, so now the compiler can replaceT
with my type and create a concrete class that's ready to be compiled and linked.To sum up, templates are blueprints for how classes should look, classes are blueprints for how an object should look.
I can't compile templates separate from their concrete instantiation because the compiler only compiles concrete types, in other words, templates at least in C++, is pure language abstraction. We have to de-abstract templates so to speak, and we do so by giving them a concrete type to deal with so that our template abstraction can transform into a regular class file and in turn, it can be compiled normally. Separating the template .h file and the template .cpp file is meaningless. It is nonsensical because the separation of .cpp and .h only is only where the .cpp can be compiled individually and linked individually, with templates since we can't compile them separately, because templates are an abstraction, therefore we are always forced to put the abstraction always together with the concrete instantiation where the concrete instantiation always has to know about the type being used.
Meaning
typename T
get's replaced during the compilation step not the linking step so if I try to compile a template withoutT
being replaced as a concrete value type that is completely meaningless to the compiler and as a result object code can't be created because it doesn't know whatT
is.It is technically possible to create some sort of functionality that will save the template.cpp file and switch out the types when it finds them in other sources, I think that the standard does have a keyword
export
that will allow you to put templates in a separate cpp file but not that many compilers actually implement this.Just a side note, when making specializations for a template class, you can separate the header from the implementation because a specialization by definition means that I am specializing for a concrete type that can be compiled and linked individually.
如果担心的是通过将 .h 编译为所有使用它的 .cpp 模块的一部分而产生的额外编译时间和二进制大小膨胀,在许多情况下,您可以做的是使模板类从非模板化基类派生接口的非类型相关部分,并且该基类可以在 .cpp 文件中拥有其实现。
If the concern is the extra compilation time and binary size bloat produced by compiling the .h as part of all the .cpp modules using it, in many cases what you can do is make the template class descend from a non-templatized base class for non type-dependent parts of the interface, and that base class can have its implementation in the .cpp file.
只是在这里添加一些值得注意的内容。当模板类的方法不是函数模板时,可以在实现文件中很好地定义它们。
myQueue.hpp:
myQueue.cpp:
Just to add something noteworthy here. One can define methods of a templated class just fine in the implementation file when they are not function templates.
myQueue.hpp:
myQueue.cpp:
单独实现的一种方式如下。
inner_foo.h
foo.tpp
foo.h
main.cpp
inner_foo.h
具有前向声明。foo.tpp
具有实现并包含inner_foo.h
;并且foo.h
将只有一行,以包含foo.tpp
。在编译时,
foo.h
的内容被复制到foo.tpp
,然后整个文件被复制到foo.h
,之后编译。这样就没有任何限制,命名也一致,换来的是多一个文件。我这样做是因为代码的静态分析器在看不到
*.tpp
中类的前向声明时会中断。在任何 IDE 中编写代码或使用 YouCompleteMe 或其他工具时,这很烦人。A way to have separate implementation is as follows.
inner_foo.h
foo.tpp
foo.h
main.cpp
inner_foo.h
has the forward declarations.foo.tpp
has the implementation and includesinner_foo.h
; andfoo.h
will have just one line, to includefoo.tpp
.On compile time, contents of
foo.h
are copied tofoo.tpp
and then the whole file is copied tofoo.h
after which it compiles. This way, there is no limitations, and the naming is consistent, in exchange for one extra file.I do this because static analyzers for the code break when it does not see the forward declarations of class in
*.tpp
. This is annoying when writing code in any IDE or using YouCompleteMe or others.这是完全正确的,因为编译器必须知道要分配的类型。因此,如果要将模板类、函数、枚举等公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像 c/cpp 文件那样进行编译。是。如果编译器不知道类型就无法编译它。在 .Net 中可以,因为所有对象都派生自 Object 类。这不是.Net。
That is exactly correct because the compiler has to know what type it is for allocation. So template classes, functions, enums,etc.. must be implemented as well in the header file if it is to be made public or part of a library (static or dynamic) because header files are NOT compiled unlike the c/cpp files which are. If the compiler doesn't know the type is can't compile it. In .Net it can because all objects derive from the Object class. This is not .Net.
我建议查看这个 gcc 页面,其中讨论了模板实例化的“cfront”和“borland”模型之间的权衡。
https://gcc.gnu.org/onlinedocs/gcc -4.6.4/gcc/Template-Instantiation.html
“borland”模型对应于作者的建议,提供完整的模板定义,并多次编译。
它包含有关使用手动和自动模板实例化的明确建议。例如,“-repo”选项可用于收集需要实例化的模板。或者另一种选择是使用“-fno-implicit-templates”禁用自动模板实例化,以强制手动模板实例化。
根据我的经验,我依赖于为每个编译单元实例化的 C++ 标准库和 Boost 模板(使用模板库)。对于我的大型模板类,我会针对我需要的类型进行一次手动模板实例化。
这是我的方法,因为我提供的是一个工作程序,而不是用于其他程序的模板库。这本书的作者 Josuttis 在模板库方面做了很多工作。
如果我真的担心速度,我想我会探索使用预编译头
https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
正在获得许多编译器的支持。但是,我认为预编译头对于模板头文件来说会很困难。
I suggest looking at this gcc page which discusses the tradeoffs between the "cfront" and "borland" model for template instantiations.
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
The "borland" model corresponds to what the author suggests, providing the full template definition, and having things compiled multiple times.
It contains explicit recommendations concerning using manual and automatic template instantiation. For example, the "-repo" option can be used to collect templates which need to be instantiated. Or another option is to disable automatic template instantiations using "-fno-implicit-templates" to force manual template instantiation.
In my experience, I rely on the C++ Standard Library and Boost templates being instantiated for each compilation unit (using a template library). For my large template classes, I do manual template instantiation, once, for the types I need.
This is my approach because I am providing a working program, not a template library for use in other programs. The author of the book, Josuttis, works a lot on template libraries.
If I was really worried about speed, I suppose I would explore using Precompiled Headers
https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
which is gaining support in many compilers. However, I think precompiled headers would be difficult with template header files.
(从封闭重复复制到这里)
我更喜欢将我的所有函数都放在
.cpp
文件中,无论它们是模板函数还是常规函数。有一种方法可以通过一些基本的#ifndef 魔法来做到这一点。您可以执行以下操作:main.cpp
myclass.hpp
myclass.cpp
以下是预编译器如何看待它。我们有两个
.cpp
文件。myclass.hpp
MYCLASS
是否未定义,并且它已myclass.cpp
MYCLASS_FUNCTIONS
MYCLASS_FUNCTIONS
是否已定义,它为MYCLASS_FUNCTIONS
是否已定义,它不myclass.hpp
MYCLASS
是否未定义,并且已myclass.cpp
myclass.hpp
MYCLASS
,因此在内部不执行任何操作,返回myclass.cpp
MYCLASS_FUNCTIONS
,它为(copying here from a closed duplicate)
I prefer to have all of my functions in the
.cpp
file, regardless of whether they are template functions or regular functions. And there is a way to do that with some basic#ifndef
magic. Here's what you can do:main.cpp
myclass.hpp
myclass.cpp
Here's how the precompiler sees it. We have two
.cpp
files.myclass.hpp
MYCLASS
is undefined, and it ismyclass.cpp
MYCLASS_FUNCTIONS
MYCLASS_FUNCTIONS
is defined, it isMYCLASS_FUNCTIONS
is defined, it isn'tmyclass.hpp
MYCLASS
is undefined, and it ismyclass.cpp
myclass.hpp
againMYCLASS
is defined so do nothing inside, return tomyclass.cpp
MYCLASS_FUNCTIONS
is defined, it is受到 Moshe 的回答的启发: https://stackoverflow.com/a/38448106/6459849
我这边的一个小贡献有一个扩展的例子。假设有一个整体的OperationSuccess,它包含一个ResponseSuccess,其中有一个通用类型。
ResponseSuccess.h
OperationSuccess.h
用法:
With motivation from Moshe's answer from: https://stackoverflow.com/a/38448106/6459849
A small contribution from my side with an extended example. Let's say there is an overall OperationSuccess and it contains a ResponseSuccess which has a generic type in it.
ResponseSuccess.h
OperationSuccess.h
Usage:
C++20 允许
auto
作为函数参数类型,这使得 缩写函数模板。编译器还需要访问函数的实现,以使用推导的参数类型实例化它。因此,实现应该在头文件中。
使用
auto
参数无法实现将单独实现保留在源文件中的替代解决方案。如果需要,可以将auto
参数替换为类型化模板参数。C++20 allows
auto
as a function parameter typeThis makes an Abbreviated function template. The compiler also needs to have access to the implementation of the function to instantiate it with the deduced parameter type. Therefore, the implementation should be in a header file.
The alternative solution of keeping the separated implementation in a source file is not possible with
auto
parameters. If this is required,auto
parameters can be replaced with typed template parameters.在头文件中编写声明和定义的另一个好主意是为了提高可读性。假设Utility.h中有这样一个模板函数:
而在Utility.cpp中:
这需要这里的每个T类都实现小于运算符(<)。当您比较两个未实现“<”的类实例时,它将引发编译器错误。
因此,如果您将模板声明和定义分开,您将无法仅读取头文件来查看此模板的来龙去脉,以便在您自己的类上使用此 API,尽管编译器会在此处告诉您关于需要覆盖哪个运算符的情况。
Another reason that it's a good idea to write both declarations and definitions in header files is for readability. Suppose there's such a template function in Utility.h:
And in the Utility.cpp:
This requires every T class here to implement the less than operator (<). It will throw a compiler error when you compare two class instances that haven't implemented the "<".
Therefore if you separate the template declaration and definition, you won't be able to only read the header file to see the ins and outs of this template in order to use this API on your own classes, though the compiler will tell you in this case about which operator needs to be overridden.
我必须编写一个模板类,这个示例对我有用
这是动态数组类的示例。
现在,在 .template 文件中,您可以像平常一样定义函数。
I had to write a template class an d this example worked for me
Here is an example of this for a dynamic array class.
Now inside you .template file you define your functions just how you normally would.