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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/495021/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:39:38  来源:igfitidea点击:

Why can templates only be implemented in the header file?

c++templatesc++-faq

提问by MainID

Quote from The C++ standard library: a tutorial and handbook:

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

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 onlyportable solution. But they are the most convenient portable solution.)

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

采纳答案by Luc Touraille

Caveat: It is notnecessary 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

脚.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

文件

// 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.

如果我的解释不够清楚,您可以查看有关此主题C++ Super-FAQ

回答by MaHuJa

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 template functions, 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 是在头文件中完全定义的,除非使用公共包含文件(预编译头?)extern template class vector<int>以防止它在使用 vector 的所有其他(1000?)文件中实例化它。

回答by Ben

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
    • uses MyClass<int>
  • foo.h
    • 声明接口 class MyClass<T>
  • 文件
    • 定义实现 class MyClass<T>
  • bar.cpp
    • 用途 MyClass<int>

Separate compilation means I should be able to compile foo.cppindependently 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.

独立汇编的手段,我应该能够编写Foo.cpp中独立地bar.cpp。编译器完全独立地对每个编译单元进行分析、优化和代码生成的所有艰苦工作;我们不需要做整个程序的分析。只有链接器需要一次处理整个程序,链接器的工作要容易得多。

bar.cppdoesn't even need to exist when I compile foo.cpp, but I should still be able to link the foo.oI already had together with the bar.oI've only just produced, without needing to recompile foo.cpp. foo.cppcould 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.

bar.cpp甚至不需要当我编译存在Foo.cpp中,但我仍然能够链接foo.o的我已经与有共同文件bar.o我刚刚产生,而无需重新编译FOO的.cppfoo.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 hadwritten each version separately. So a template is literallya template; a class template is nota class, it's a recipe for creating a new class for each Twe encounter. A template cannot be compiled into code, only the result of instantiating the template can be compiled.

“实例化风格的多态性”意味着模板MyClass<T>并不是真正的泛型类,可以编译为可以为任何值工作的代码T。这会增加开销,如拳击,需要对函数指针传递给分配器和构造等的C ++模板的目的是为了避免写几乎相同class MyClass_intclass MyClass_float等等,但仍然能够编译代码,结束了主要是好像我们分别编写了每个版本。所以模板实际上就是一个模板;类模板不是类,它是为T我们遇到的每个类创建新类的方法。模板不能编译成代码,只能编译模板实例化的结果。

So when foo.cppis compiled, the compiler can't see bar.cppto 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.cppis 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>需要它。它可以看到 template MyClass<T>,但不能为此发出代码(它是一个模板,而不是一个类)。而当bar.cpp被编译时,编译器可以看到它需要创建一个MyClass<int>,但是它看不到模板MyClass<T>(只有它在foo.h 中的接口)所以它不能创建它。

If foo.cppitself uses MyClass<int>, then code for that will be generated while compiling foo.cpp, so when bar.ois linked to foo.othey 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.cppto use the template as a templateand instantiate it on whatever types it likes; it can only use pre-existing versions of the templated class that the author of foo.cppthought 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>
  • baz.cpp
    • 声明和实现class BazPrivate,并使用MyClass<BazPrivate>

There is no possible way that this could work unless we either

除非我们

  1. Have to recompile foo.cppevery time we change any other file in the program, in case it added a new novel instantiation of MyClass<T>
  2. Require that baz.cppcontains (possibly via header includes) the full template of MyClass<T>, so that the compiler can generate MyClass<BazPrivate>during compilation of baz.cpp.
  1. 每次我们更改程序中的任何其他文件时都必须重新编译foo.cpp,以防它添加了一个新的实例化MyClass<T>
  2. 要求baz.cpp包含(可能通过头文件包含)的完整模板MyClass<T>,以便编译器可以MyClass<BazPrivate>在编译baz.cpp 时生成。

Nobody likes (1), because whole-program-analysis compilation systems take foreverto compile , and because it makes it impossible to distribute compiled libraries without the source code. So we have (2) instead.

没有人喜欢 (1),因为全程序分析编译系统需要很长时间来编译,并且因为它使得在没有源代码的情况下无法分发已编译的库。所以我们有(2)代替。

回答by David Hanak

Templates need to be instantiatedby 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.cppand used in b.cpp. When a.cppis compiled, it is not necessarily known that the upcoming compilation b.cppwill 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.cpp和使用的场景b.cpp。当a.cpp被编译,它不一定知道,即将到来的编纂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 指出的那样,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(还?)。

回答by DevSolar

Actually, prior to C++11 the standard defined the exportkeyword that wouldmake it possible to declare templates in a header file and implement them elsewhere.

实际上,之前C ++ 11标准中定义的export关键字使其能够在头文件中声明的模板和其他地区实施。

None of the popular compilers implemented this keyword. The only one I know about is 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).

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

As a result, the ISO C++ standard committee decided to remove the exportfeature of templates with C++11.

因此,ISO C++ 标准委员会决定在exportC++11 中删除模板的特性。

回答by Anton Gogolev

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

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

There is an exportkeyword which is supposed to mitigate this problem, but it's nowhere close to being portable.

有一个export关键字应该可以缓解这个问题,但它离便携还差得很远。

回答by Germán Diago

Templates must be used in headers because the compiler needs to instantiate different versions of the code, depending on the parameters given/deduced for template parameters. 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 .cppfile, 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 exportkeyword that was meant to be used for separate compilation. The exportfeature is deprecated in C++11and, AFAIK, only one compiler implemented it. You shouldn't make use of export. Separate compilation is not possible in C++or C++11but maybe in C++17, if concepts make it in, we could have some way of separate compilation.

有一个带有export关键字的功能,旨在用于单独编译。该export功能已被弃用C++11,AFAIK,只有一个编译器实现了它。你不应该使用export. 单独的编译是不可能的C++或者C++11但也许C++17,如果概念使其在,我们可以有单独的编译的一些方法。

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 paperrecently presented at the standards commitee 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.

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

回答by lafrecciablu

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).
Each user of the template includes that header file and uses the typedef.

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

A schematic example:

示意图示例:

MyTemplate.h:

我的模板.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:

我的模板.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:

主.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).

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

回答by Beno?t

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
    }
};

回答by Nikos

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:

我的队列.hpp:

template <class T> 
class QueueA {
    int size;
    ...
public:
    template <class T> T dequeue() {
       // implementation here
    }

    bool isEmpty();

    ...
}    


myQueue.cpp:

我的队列.cpp:

// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
    return this->size == 0;
}


main()
{
    QueueA<char> Q;

    ...
}