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:
无论如何,代码失败的原因是,在实例化模板时,编译器会使用给定的模板参数创建一个新类。例如:
template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};
// somewhere in a .cpp
Foo<int> f;
When reading this line, the compiler will create a new class (let's call it FooInt
), which is equivalent to the following:
当读取这一行时,编译器将创建一个新的类(我们称之为FooInt),它等效于以下内容:
struct FooInt
{
int bar;
void doSomething(int param) {/* do stuff using int */}
};
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.
因此,编译器需要访问这些方法的实现,以便使用模板参数(在本例中为int)实例化它们。如果这些实现不在头中,它们将不可访问,因此编译器将无法实例化模板。
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.
一个常见的解决方案是在头文件中写入模板声明,然后在实现文件(例如.tpp)中实现类,并在头的末尾包含此实现文件。
Foo.h
Foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
Foo.tpp
Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
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.h
// no implementation
template <typename T> struct Foo { ... };
Foo.cpp
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
If my explanation isn't clear enough, you can have a look at the C++ Super-FAQ on this subject.
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:
让我们更接近具体的解释。假设我有以下文件:
- foo.h
- declares the interface of
class MyClass<T>
- foo.cpp
- defines the implementation of
class MyClass<T>
- bar.cpp
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编译foo.cpp。编译器完全独立地在每个编译单元上完成所有的分析、优化和代码生成工作;我们不需要做整个程序的分析。只有链接器需要同时处理整个程序,而且链接器的工作要简单得多。
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.
当我编译foo.cpp时,bar.cpp甚至不需要存在,但我应该仍然能够将我已经拥有的foo.o与bar.o链接在一起。我刚刚制作了foo.cpp,不需要重新编译。foo.cpp甚至可以编译成一个动态库,在没有foo.cpp的情况下分发到其他地方,并与他们在我编写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 of T
. 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 identical class 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 each T
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 template MyClass<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 a MyClass<int>
, but it can't see the template MyClass<T>
(only its interface in foo.h) so it can't create it.
因此,当编译foo.cpp时,编译器无法看到bar.cpp来知道需要MyClass<int>。它可以看到模板MyClass<T>,但不能为此发出代码(它是一个模板,而不是一个类)。当编译bar.cpp时,编译器可以看到它需要创建一个MyClass<int>,但它看不到模板MyClass<t>(只有它在foo.h中的接口),所以它无法创建它。
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.
如果foo.cpp本身使用MyClass<int>,那么在编译foo.cpp时会生成相应的代码,因此当bar.o链接到foo.o时,它们可以连接起来并工作。我们可以利用这一事实,通过编写单个模板,在.cpp文件中实现一组有限的模板实例化。但是bar.cpp没有办法将模板用作模板,并在它喜欢的任何类型上实例化它;它只能使用foo.cpp的作者认为要提供的模板化类的预先存在的版本。
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:
您可能会认为,在编译模板时,编译器应该“生成所有版本”,在链接过程中过滤掉从未使用过的版本。除了这种方法将面临的巨大开销和极端困难之外,因为指针和数组等“类型修饰符”功能甚至只允许内置类型产生无限数量的类型,当我现在通过添加以下内容来扩展程序时会发生什么:
- baz.cpp
- declares and implements
class BazPrivate
, and uses MyClass<BazPrivate>
There is no possible way that this could work unless we either
There is no possible way that this could work unless we either
- Have to recompile foo.cpp every time we change any other file in the program, in case it added a new novel instantiation of
MyClass<T>
- Require that baz.cpp contains (possibly via header includes) the full template of
MyClass<T>
, so that the compiler can generate MyClass<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.
没有人喜欢(1),因为整个程序分析编译系统需要很长时间才能编译,而且如果没有源代码,就不可能分发编译后的库。所以我们改为(2)。
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.
如果您在实现cpp文件的底部对模板将要使用的所有类型进行显式实例化,那么链接器将能够像往常一样找到它们。
Edit: Adding example of explicit template instantiation. Used after the template has been defined, and all member functions has been defined.
编辑:添加显式模板实例化的示例。在定义模板并定义所有成员函数后使用。
template class vector<int>;
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.
上面的例子是非常无用的,因为vector是在头中完全定义的,除非一个公共的包含文件(预编译头?)使用外部模板类vector<int>,以防止它在所有其他使用vector的(1000?)文件中实例化它。
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 in a.cpp
and used in b.cpp
. When a.cpp
is compiled, it is not necessarily known that the upcoming compilation b.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.
在将模板实际编译为对象代码之前,需要由编译器实例化模板。只有在已知模板参数的情况下才能实现此实例化。现在想象一个场景,其中一个模板函数在a.h中声明,在a.pp中定义,并在b.cpp中使用。编译a.pp时,不一定知道即将进行的编译b.cpp将需要一个模板实例,更不用说具体的实例了。对于更多的头文件和源文件,情况可能会很快变得更加复杂。
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?).
有人可能会说,编译器可以更聪明地“前瞻”模板的所有用途,但我相信创建递归或其他复杂场景并不困难。AFAIK,编译器不会做这样的外观。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持(还没有?)。
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:
Phantom advantage #1: Hiding source code. Many users, have said that they expect that by using export they will
no longer have to ship definitions for member/nonmember function templates and member functions of class
templates. This is not true. With export, library writers still have to ship full template source code or its direct
equivalent (e.g., a system-specific parse tree) because the full information is required for instantiation. [...]
Phantom advantage #2: Fast builds, reduced dependencies. Many users expect that export will allow true separate
compilation of templates to object code which they expect would allow faster builds. It doesn’t because the
compilation of exported templates is indeed separate but not to object code. Instead, export almost always makes
builds slower, because at least the same amount of compilation work must still be done at prelink time. Export
does not even reduce dependencies between template definitions because the dependencies are intrinsic,
independent of file organization.
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.
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.
有一个export关键字应该可以缓解这个问题,但它还远远不能移植。
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.
模板通常用于头中,因为编译器需要实例化不同版本的代码,这取决于为模板参数给定/推导的参数,而且(作为程序员)让编译器多次重新编译同一代码并在以后进行重复数据消除更容易。请记住,模板并不直接表示代码,而是用于该代码的多个版本的模板。在.cpp文件中编译非模板函数时,您正在编译一个具体的函数/类。模板的情况并非如此,模板可以用不同的类型实例化,即,用具体类型替换模板参数时必须发出具体代码。
There was a feature with the export
keyword that was meant to be used for separate compilation.
The export
feature is deprecated in C++11
and, AFAIK, only one compiler implemented it.
You shouldn't make use of export
.
Separate compilation is not possible in C++
or C++11
but maybe in C++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
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).
我采用了以下方法,它也适用于较旧的编译器(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).
对于每个模板的使用,在其自己的头文件中都有一个typedef(从UML模型生成)。它的主体包含实例化(它最终在一个库中链接)。
Each user of the template includes that header file and uses the typedef.
模板的每个用户都包含该头文件并使用typedef。
A schematic example:
示意图示例:
MyTemplate.h:
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:
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:
MyInstantiatedTemplate.h:
#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"
typedef MyTemplate< int > MyInstantiatedTemplate;
#endif
MyInstantiatedTemplate.cpp:
MyInstantiatedTemplate.cpp:
#include "MyTemplate.cpp"
template class MyTemplate< int >;
main.cpp:
main.cpp:
#include "MyInstantiatedTemplate.h"
int main()
{
MyInstantiatedTemplate m(100);
m.dump();
return 0;
}
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.
这意味着定义模板类的方法实现的最可移植的方法是在模板类定义中定义它们。
template < typename ... >
class MyClass
{
int myMethod()
{
// Not just declaration. Add method implementation here
}
};
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.
当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.cpp文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为main.cpp中包含的.h文件没有实现YET。这些已经准备好与另一个对象文件链接,该对象文件定义了模板的实现,因此您就有了一个完整的a.out可执行文件。
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.
然而,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例化生成代码,因此简单地编译一个与它的头文件分离的模板是行不通的,因为它们总是齐头并进,因为每个模板实例化实际上都是一个全新的类。在一个常规类中,你可以将.h和.cpp分开,因为.h是该类的蓝图,.cpp是原始实现,所以任何实现文件都可以定期编译和链接,但要使用模板。h是类应该如何看起来的蓝图,而不是对象应该如何看起来,这意味着模板.cpp文件不是类的原始常规实现,它只是一个类的蓝图,所以不能编译.h模板文件的任何实现,因为你需要一些具体的东西来编译,从这个意义上说,模板是抽象的。
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 replace T
with my type and create a concrete class that's ready to be compiled and linked.
因此,模板永远不会单独编译,只会在其他源文件中有具体实例化的地方编译。然而,具体的实例化需要知道模板文件的实现,因为简单地使用.h文件中的具体类型修改typename T是行不通的,因为.cpp要链接什么,我以后找不到它,因为记住模板是抽象的,无法编译,所以我现在必须给出实现,这样我就知道该编译和链接什么,现在我有了实现,它就链接到了封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道使用我提供的类型时该类应该是什么样子,我就无法做到这一点,除非我通知模板实现的编译器,所以现在编译器可以用我的类型替换t,并创建一个可以编译和链接的具体类。
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 without T
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 what T
is.
这意味着类型名T在编译步骤而不是链接步骤中被替换,所以如果我试图在没有将T替换为具体值类型的情况下编译模板,这对编译器来说完全没有意义,因此无法创建对象代码,因为它不知道T是什么。
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.
从技术上讲,可以创建一些功能来保存template.cpp文件,并在其他源中找到类型时切换它们。我认为该标准确实有一个关键字导出,可以将模板放在一个单独的cpp文件中,但实际上并没有多少编译器实现这一点。
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.
顺便说一句,当为模板类进行专门化时,您可以将头与实现分离,因为根据定义进行专门化意味着我专门化的是可以单独编译和链接的具体类型。
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.hpp:
template <class T>
class QueueA {
int size;
...
public:
template <class T> T dequeue() {
// implementation here
}
bool isEmpty();
...
}
myQueue.cpp:
myQueue.cpp:
// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
return this->size == 0;
}
main()
{
QueueA<char> Q;
...
}
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.
如果问题是将.h作为使用它的所有.cpp模块的一部分进行编译所产生的额外编译时间和二进制大小膨胀,那么在许多情况下,您可以做的是使模板类从接口的非类型依赖部分的非模板化基类派生,并且该基类可以在.cpp文件中实现。
A way to have separate implementation is as follows.
单独实现的方法如下。
inner_foo.h
inner_fo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
foo.tpp
foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
foo.h
foo.h
#include <foo.tpp>
main.cpp
main.cpp
#include <foo.h>
inner_foo.h
has the forward declarations. foo.tpp
has the implementation and includes inner_foo.h
; and foo.h
will have just one line, to include foo.tpp
.
inner_fo.h具有正向声明。foo.tpp具有实现,并包含inner_fo.h;而foo.h将只有一行,包括foo.tpp。
On compile time, contents of foo.h
are copied to foo.tpp
and then the whole file is copied to foo.h
after which it compiles. This way, there is no limitations, and the naming is consistent, in exchange for one extra file.
在编译时,将foo.h的内容复制到foo.tpp,然后将整个文件复制到foo.h,然后进行编译。通过这种方式,没有任何限制,并且命名是一致的,以换取一个额外的文件。
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.
我这样做是因为当代码的静态分析器在*.tpp中看不到类的正向声明时,它就会中断。在任何IDE中编写代码或使用YouCompleteMe或其他程序时,这都很烦人。
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.
这是完全正确的,因为编译器必须知道要分配的类型。模板类、函数、枚举等。。如果要将其公开或作为库(静态或动态)的一部分,则必须在头文件中实现,因为头文件与编译的c/cpp文件不同。如果编译器不知道类型是什么,就不能编译它。在.Net中,它可以,因为所有对象都派生自Object类。这不是.Net。
I suggest looking at this gcc page which discusses the tradeoffs between the "cfront" and "borland" model for template instantiations.
我建议看一下这个gcc页面,它讨论了模板实例化的“cfront”和“borland”模型之间的权衡。
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
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.
“borland”模型符合作者的建议,提供了完整的模板定义,并对其进行了多次编译。
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.
它包含关于使用手动和自动模板实例化的明确建议。例如,“-repo”选项可用于收集需要实例化的模板。或者另一个选项是使用“-fno隐式模板”禁用自动模板实例化,以强制手动模板实例化。
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.
这是我的方法,因为我提供的是一个工作程序,而不是用于其他程序的模板库。这本书的作者Josuttis在模板库方面做了很多工作。
If I was really worried about speed, I suppose I would explore using Precompiled Headers
https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
如果我真的担心速度,我想我会探索使用预编译头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.
这在许多编译器中获得了支持。但是,我认为预编译的头文件对于模板头文件来说会很困难。
(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:
我更喜欢将我的所有函数都放在.cpp文件中,不管它们是模板函数还是常规函数。有一种方法可以通过一些基本的#ifndef魔术来做到这一点。以下是您可以做的:
main.cpp
main.cpp
#include "myclass.hpp"
int main()
{
// ...
}
myclass.hpp
myclass.hpp
#ifndef MYCLASS
#define MYCLASS
template<class T>
class MyClass
{
T val;
public:
MyClass(T val_);
}
#define MYCLASS_FUNCTIONS
#include "myclass.cpp"
#endif
myclass.cpp
myclass.cpp
#ifndef MYCLASS_FUNCTIONS
#include "myclass.hpp"
// regular functions:
// ...
#else
// template functions:
template<class T>
MyClass<T>::MyClass(T val_)
:val(val_)
{}
// ...
#endif
Here's how the precompiler sees it. We have two .cpp
files.
预编译器是这样看的。我们有两个.cpp文件。
- When we compile main.cpp we:
- include
myclass.hpp
- check that
MYCLASS
is undefined, and it is
- define it
- give compiler the definitions of the generated class (from template class)
- include
myclass.cpp
- define
MYCLASS_FUNCTIONS
- check if
MYCLASS_FUNCTIONS
is defined, it is
- give compiler the definitions of the generated functions (from template functions)
- When we compile myclass.cpp
- check if
MYCLASS_FUNCTIONS
is defined, it isn't
- include
myclass.hpp
- check that
MYCLASS
is undefined, and it is
- define it
- give compiler the definitions of the class
- include
myclass.cpp
- include
myclass.hpp
again
- this time
MYCLASS
is defined so do nothing inside, return to myclass.cpp
- check if
MYCLASS_FUNCTIONS
is defined, it is
- give compiler the definition of the generated functions (from template functions)
- exit include twice
- pass to the compiler all the regular functions
With motivation from Moshe's answer from: https://stackoverflow.com/a/38448106/6459849
Moshe的回答激励了我们: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.
这是我的一个小贡献,还有一个扩展的例子。假设有一个整体OperationSuccess,它包含一个具有泛型类型的ResponseSuccess。
ResponseSuccess.h
响应成功.h
template <class T>
class ResponseSuccess {
public:
ResponseSuccess(const ResponseStatus responseStatus, const T& data) :
m_responseStatus(responseStatus),
m_data(data) {}
~ResponseSuccess() = default;
// Basis requirement, have Copy/Move constructor/delete assignment operator
ResponseStatus getResponseStatus() const {
return m_responseStatus;
}
T getData() const {
return m_data;
};
private:
ResponseStatus m_responseStatus;
T m_data;
};
OperationSuccess.h
操作成功.h
template <class T>
class OperationResponse {
public:
explicit OperationResponse(ResponseSuccess<T> responseSuccess) :
m_responseSuccess(std::move(responseSuccess)) {}
~OperationResponse() = default;
// Basis requirement, have Copy/Move constructor/delete assignment operator
ResponseSuccess<T> getResponseSuccess() const {
return m_responseSuccess;
}
private:
ResponseSuccess<T> m_responseSuccess;
// have a failure, in case required
};
Usage:
用法:
MyObject myObj(<ctor_args>);
ResponseSuccess<MyObject> responseSuccess(ResponseStatus::SUCCESS, myObj);
OperationResponse<MyObject> successOperationResponse(responseSuccess);
..
// Fetches the response -> successOperationResponse.getResponseSuccess();
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:
在头文件中同时编写声明和定义是一个好主意的另一个原因是为了可读性。假设Utility.h中有这样一个模板函数:
template <class T>
T min(T const& one, T const& theOther);
And in the Utility.cpp:
在Utility.cpp中:
#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
return one < other ? one : other;
}
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 "<".
这需要这里的每个T类实现小于运算符(<)。当您比较尚未实现“<”的两个类实例时,它将引发编译器错误。
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.
因此,如果将模板声明和定义分开,则无法仅读取头文件来查看此模板的输入和输出,以便在自己的类上使用此API,尽管在这种情况下编译器会告诉您需要重写哪个运算符。
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.
下面是一个动态数组类的例子。
#ifndef dynarray_h
#define dynarray_h
#include <iostream>
template <class T>
class DynArray{
int capacity_;
int size_;
T* data;
public:
explicit DynArray(int size = 0, int capacity=2);
DynArray(const DynArray& d1);
~DynArray();
T& operator[]( const int index);
void operator=(const DynArray<T>& d1);
int size();
int capacity();
void clear();
void push_back(int n);
void pop_back();
T& at(const int n);
T& back();
T& front();
};
#include "dynarray.template" // this is how you get the header file
#endif
Now inside you .template file you define your functions just how you normally would.
现在,在你的.template文件中,你可以按照通常的方式定义你的函数。
template <class T>
DynArray<T>::DynArray(int size, int capacity){
if (capacity >= size){
this->size_ = size;
this->capacity_ = capacity;
data = new T[capacity];
}
// for (int i = 0; i < size; ++i) {
// data[i] = 0;
// }
}
template <class T>
DynArray<T>::DynArray(const DynArray& d1){
//clear();
//delete [] data;
std::cout << "copy" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
}
template <class T>
DynArray<T>::~DynArray(){
delete [] data;
}
template <class T>
T& DynArray<T>::operator[]( const int index){
return at(index);
}
template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
if (this->size() > 0) {
clear();
}
std::cout << "assign" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
//delete [] d1.data;
}
template <class T>
int DynArray<T>::size(){
return size_;
}
template <class T>
int DynArray<T>::capacity(){
return capacity_;
}
template <class T>
void DynArray<T>::clear(){
for( int i = 0; i < size(); ++i){
data[i] = 0;
}
size_ = 0;
capacity_ = 2;
}
template <class T>
void DynArray<T>::push_back(int n){
if (size() >= capacity()) {
std::cout << "grow" << std::endl;
//redo the array
T* copy = new T[capacity_ + 40];
for (int i = 0; i < size(); ++i) {
copy[i] = data[i];
}
delete [] data;
data = new T[ capacity_ * 2];
for (int i = 0; i < capacity() * 2; ++i) {
data[i] = copy[i];
}
delete [] copy;
capacity_ *= 2;
}
data[size()] = n;
++size_;
}
template <class T>
void DynArray<T>::pop_back(){
data[size()-1] = 0;
--size_;
}
template <class T>
T& DynArray<T>::at(const int n){
if (n >= size()) {
throw std::runtime_error("invalid index");
}
return data[n];
}
template <class T>
T& DynArray<T>::back(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[size()-1];
}
template <class T>
T& DynArray<T>::front(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[0];
}
更多回答
Actually the explicit instantiation needs to be in a .cpp file which has access to the definitions for all of Foo's member functions, rather than in the header.
实际上,显式实例化需要在.cpp文件中,该文件可以访问Foo所有成员函数的定义,而不是在头中。
"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" But why is an implementation in the .cpp file not accessible to the compiler? A compiler can also access .cpp information, how else would it turn them into .obj files? EDIT: answer to this question is in the link provided in this answer...
“编译器需要访问这些方法的实现,才能用模板参数(在本例中为int)实例化它们。如果这些实现不在头中,它们就不可访问。”但是,为什么编译器不能访问.cpp文件中的实现呢?编译器也可以访问.cpp信息,否则如何将它们转换为.obj文件?编辑:这个问题的答案在这个答案中提供的链接中。。。
I don't think this explains the question that clearly, the key thing is obviously related with the compilation UNIT which is not mentioned in this post
我认为这并不能清楚地解释这个问题,关键显然与本帖子中没有提到的汇编单位有关
@Gabson: structs and classes are equivalent with the exception that the default access modifier for classes is "private", while it is public for structs. There are some other tiny differences that you can learn by looking at this question.
@Gabson:structs和类是等价的,只是类的默认访问修饰符是“private”,而structs是public。通过研究这个问题,你可以了解到其他一些细微的差异。
I've added a sentence at the very start of this answer to clarify that the question is based on a false premise. If somebody asks "Why is X true?" when in fact X is not true, we should quickly reject that assumption.
我在这个答案的开头加了一句话,以澄清这个问题是基于一个错误的前提。如果有人问“为什么X是真的?”而事实上X不是真的,我们应该很快拒绝这个假设。
emphasized quote a template is literally a template; a class template is not a class, it's a recipe for creating a new class for each T we encounter
强调引用模板实际上就是一个模板;类模板不是一个类,它是为我们遇到的每个T创建一个新类的配方
@Birger You should be able to do it from any file that has access to the full template implementation (either because it's in the same file or via header includes).
@Birger您应该能够从任何可以访问完整模板实现的文件中执行此操作(因为它在同一文件中或通过头包含)。
@ajeh It's not rhetoric. The question is "why do you have to implement templates in a header?", so I explained the technical choices the C++ language makes that lead to this requirement. Before I wrote my answer others already provided workarounds that are not full solutions, because there can't be a full solution. I felt those answers would be complemented by a fuller discussion of the "why" angle of the question.
imagine it this way folks... if you weren't using templates (to efficiently code what you needed), you'd only be offering a few versions of that class anyway. so you have 3 options. 1). don't use templates. (like all other classes/functions, nobody cares that others can't alter the types) 2). use templates, and document which types they can use. 3). give them the whole implementation (source) bonus 4). give them the whole source in case they want to make a template from another one of your classes ;)
想象一下,伙计们。。。如果你不使用模板(有效地编写你需要的代码),那么你只会提供该类的几个版本。所以你有3个选择。1) 。不要使用模板。(就像所有其他类/函数一样,没有人关心其他类/功能不能更改类型)2)。使用模板,并记录它们可以使用的类型。3) 。给他们整个实现(源代码)奖励4)。为他们提供完整的源代码,以防他们想从您的另一个类中制作模板;)
@VoB Yes, a .tpp
file in that sense is just a naming convention for a kind of header file. A "header file" isn't something specific to the C++ compiler, it's just what we call a file that we intend to include into other compilation units by using #include
. If it helps you work with your code to have template implementations in a separate file from the one that describes the interface of a .cpp file, and to give those template implementation files a specific extension like .tpp
, then go for it! The compiler doesn't know or care about the difference, but it can help humans.
Ugh. Good answer, but no real clean solution. Listing out all possible types for a template does not seem to go with what a template is supposed to be.
啊。答案很好,但没有真正干净的解决方案。列出模板的所有可能类型似乎并不符合模板的预期。
This can be good in many cases but generally breaks the purpose of template which is meant to allow you to use the class with any type
without manually listing them.
这在很多情况下都是好的,但通常会破坏模板的目的,模板的目的是允许您将类与任何类型一起使用,而无需手动列出它们。
vector
is not a good example because a container is inherently targeting "all" types. But it does happen very frequently that you create templates that are only meant for a specific set of types, for instance numeric types: int8_t, int16_t, int32_t, uint8_t, uint16_t, etc. In this case, it still makes sense to use a template, but explicitly instantiating them for the whole set of types is also possible and, in my opinion, recommended.
vector并不是一个好例子,因为容器本质上是针对“所有”类型的。但是,创建只用于特定类型集的模板的情况确实非常频繁,例如数字类型:int8_t、int16_t、int 32_t、uint8_t和uint16_t等。在这种情况下,使用模板仍然有意义,但也可以为整个类型集显式实例化它们,在我看来,这是推荐的。
Used after the template has been defined, "and all member functions has been defined". Thanks !
在定义了模板之后使用,“并且定义了所有成员函数”。谢谢
I feel like I'm missing something … I put the explicit instantiation for two types into the class's .cpp
file and the two instantiations are referred to from other .cpp
files, and I still get the linking error that the members are not found.
我觉得我遗漏了一些东西…我把两个类型的显式实例化放在类的.cpp文件中,这两个实例化是从其他.cpp文件引用的,但我仍然会得到找不到成员的链接错误。
"export" is standard, but it's just hard to implement so most of the compiler teams just haven't done yet.
“导出”是标准的,但它很难实现,所以大多数编译器团队还没有实现。
export doesn't eliminate the need for source disclosure, nor does it reduce compile dependencies, while it requires a massive effort from compiler builders. So Herb Sutter himself asked compiler builders to 'forget about' export. As the time investment needed would be better spend elsewhere...
导出并没有消除对源代码公开的需求,也没有减少编译依赖性,同时它需要编译器构建者付出巨大的努力。因此HerbSutter自己要求编译器构建者“忘记”导出。由于所需的时间投资最好花在其他地方。。。
So I don't think export isn't implemented 'yet'. It'll probably never get done by anyone else than EDG after the others saw how long it took, and how little was gained
所以我不认为出口还没有实施。在其他人看到它花了多长时间,却收获甚微之后,除了EDG,其他人可能永远不会完成它
If that interests you, the paper is called "Why we can't afford export", it's listed on his blog (gotw.ca/publications) but no pdf there (a quick google should turn it up though)
如果你对此感兴趣,这篇论文名为“为什么我们负担不起出口”,它列在他的博客(gotw.ca/publications)上,但没有pdf(不过,谷歌应该会快速打开它)
Ok, thanks for good example and explanation. Here is my question though: why compiler cannot figure out where template is called, and compile those files first before compiling definition file? I can imagine it can be done in a simple case... Is the answer that interdependencies will mess up the order pretty fast?
好的,谢谢你的好例子和解释。但我的问题是:为什么编译器不能弄清楚在哪里调用模板,并在编译定义文件之前先编译这些文件?我可以想象它可以在一个简单的情况下完成。。。答案是相互依赖会很快扰乱秩序吗?
@DevSolar : this paper is political, repetitive and badly written. that's not usual standard level prose there. Uneedingly long and boring, saying basically 3 times the same things accross tens of pages. But I am now informed that export is not export. That's a good intel !
@DevSolar:这篇论文是政治性的,重复性强,写得不好。这不是通常的标准水平的散文。冗长乏味,几十页纸上基本上说了三遍同样的话。但我现在被告知,出口不是出口。这是个好情报!
@v.oddou: Good developer and good technical writer are two seperate skillsets. Some can do both, many can't. ;-)
@v.oddou:好的开发人员和好的技术编写人员是两种不同的技能。有些人两者都能,很多人做不到
@v.oddou The paper isn't just badly written, it's disinformation. Also it's a spin on reality: what are actually extremely strong arguments for exports are mixed in a way to make it sound like they are against export: “discovering numerous ODRrelated holes in the standard in the presence of export. Before export, ODR violations didn’t have to be diagnosed by the compiler. Now it’s necessary because you need to combine internal data structures from different translation units, and you can’t combine them if they’re actually representing different things, so you need to do the checking.”
@v.oddou这篇论文不仅写得不好,而且是虚假信息。此外,这也是对现实的歪曲:实际上支持出口的非常有力的论据在某种程度上是混合的,听起来像是反对出口:“在导出的情况下,在标准中发现了许多与ODR相关的漏洞。在导出之前,编译器不必诊断ODR违规。现在这是必要的,因为你需要组合来自不同翻译单元的内部数据结构,如果它们实际上代表不同的东西,你就不能组合它们,所以你需要进行检查。”
@DevSolar I still don't see a case made against export in the paper. (I see a case for export.)
@DevSolar我仍然没有在报纸上看到反对出口的案例。(我看到一个出口的箱子。)
Why can't I implement them in .cpp file with the keyword "inline"?
为什么我不能用关键字“inline”在.cpp文件中实现它们?
You can, and you don't have to put "inline" even. But you'd be able to use them just in that cpp file and nowhere else.
你可以,甚至不必把“内联”。但是您可以只在cpp文件中使用它们,而不在其他任何地方使用。
This is almost the most accurate answer, except "that means those compilers won't allow them to be defined in non-header files such as .cpp files" is patently false.
这几乎是最准确的答案,除了“这意味着那些编译器不允许在非头文件(如.cpp文件)中定义它们”显然是错误的。
As of now I know of know implementation in the common compilers for modules.
到目前为止,我知道模块的常见编译器中的实现。
I like this approach with the exception of the MyInstantiatedTemplate.h
file and added MyInstantiatedTemplate
type. It's a little cleaner if you don't use that, imho. Checkout my answer on a different question showing this: stackoverflow.com/a/41292751/4612476
除了MyInstantiatedTemplate.h文件和添加了MyInstantiattedTemplate类型之外,我喜欢这种方法。如果你不用的话,它会更干净一点,imho。请查看我对另一个问题的回答:stackoverflow.com/a/41297251/4612476
This takes best of two worlds. I wish this answer was rated higher! Also see the link above for a slightly cleaner implementation of the same idea.
这需要两全其美。我希望这个答案被评得更高!另请参阅上面的链接,以获得相同想法的稍微干净的实现。
For real man???If that's true then your answer should be checked as correct one.Why does anyone need all those hacky voodo stuff if you can just define non template member methods in .cpp?
对于真正的男人???如果这是真的,那么你的答案应该被检查为正确的。如果你可以在.cpp中定义非模板成员方法,为什么有人需要所有这些时髦的voodo东西?
Well that doesn't work.At least on MSVC 2019,getting unresolved external symbol for a member function of template class.
那不管用。至少在MSVC 2019上,正在为模板类的成员函数获取未解析的外部符号。
I don't have MSVC 2019 to test. This is allowed by the C++ standard. Now, MSVC is notorious for not always adhering to the rules. If you haven't already, try Project Settings -> C/C++ -> Language -> Conformance Mode -> Yes (permissive-).
This exact example works but then you can't call isEmpty
from any other translation unit besides myQueue.cpp
...
这个确切的例子有效,但你不能从myQueue.cpp之外的任何其他翻译单元调用isEmpty。。。
so this can be a good strategy for moving bulky functions to .cpp files and declaring them private, while the public functions stay in header file and call them.
因此,这是一个很好的策略,可以将庞大的函数移动到.cpp文件并声明它们为私有函数,而公共函数则保留在头文件中并调用它们。
This response should be modded up quite more. I "independently" discovered your same approach and was specifically looking for somebody else to have used it already, since I'm curious if it's an official pattern and whether it's got a name. My approach is to implement a class XBase
wherever I need to implement a template class X
, putting the type-dependent parts in X
and all the rest in XBase
.
这种反应应该被更多地修改。我“独立”发现了你同样的方法,并专门寻找其他人已经使用过它,因为我很好奇它是否是官方模式,是否有名字。我的方法是在任何需要实现模板类X的地方实现类XBase,将依赖类型的部分放在X中,其余部分放在XBase中。
s/inner_foo/foo/g and include foo.tpp at the end of foo.h. One less file.
s/inner_fo/foo/g,并在foo.h的末尾包含foo.tpp。少一个文件。
user246672 had it slightly wrong -- just include the .tpp
files (I use .ft
) in the .cpp
files that need them.
user246672有点错误——只需将.tpp文件(我使用.ft)包含在需要它们的.cpp文件中即可。
"header files are NOT compiled" - that's a really odd way of describing it. Header files can be part of a translation unit, just like a "c/cpp" file.
“头文件未编译”-这是一种非常奇怪的描述方式。头文件可以是翻译单元的一部分,就像“c/cpp”文件一样。
In fact, it's almost the opposite of the truth, which is that header files are very frequently compiled many times, whereas a source file is usually compiled once.
事实上,这几乎与事实相反,即头文件经常被编译多次,而源文件通常被编译一次。
@cz Did you read the code?
@cz你读代码了吗?
@cz Take a look at includes. myclass.o will have only non-template functions.
@cz看看includes。myclass.o将只有非模板函数。
Ah, got it thanks! Wasn't seeing that there are also regular functions defined in the .h
.
啊,收到了,谢谢!没有看到.h中也定义了正则函数。
@cz Do you happen to mean declared, because there are no function definitions in my .hpp
@cz你的意思是声明的吗,因为我的.hpp中没有函数定义
Most people would define a header file to be anything that propagates definitions to source files. So you may have decided to use the file extension ".template" but you've written a header file.
大多数人会将头文件定义为将定义传播到源文件的任何内容。因此,您可能已经决定使用文件扩展名“.template”,但您已经编写了一个头文件。
我是一名优秀的程序员,十分优秀!