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

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

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

为什么是这样?

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

答案

这是没有必要把落实在头文件中,看到这个答案的末尾替代解决方案。

无论如何,您的代码失败的原因是,在实例化模板时,编译器会使用给定的 template 参数创建一个新类。例如:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f;

阅读此行时,编译器将创建一个新类(我们将其FooInt ),其等效于以下内容:

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

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

常见的解决方案是将模板声明写在头文件中,然后在实现文件(例如. 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
}

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

替代解决方案

另一个解决方案是使实现分离,并显式实例化所需的所有模板实例:

// 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 文件的底部,对模板将使用的所有类型进行显式实例化,则链接程序将能够照常查找它们。

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

template class vector<int>;

这将实例化该类及其所有成员函数(仅对链接器可用)。类似的语法适用于模板函数,因此,如果您有非成员运算符重载,则可能需要对它们执行相同的操作。

上面的示例是毫无用处的,因为矢量完全在标头中定义,除非公共包含文件(预编译的标头?)使用extern template class vector<int>以避免在所有其他 (1000?)文件中实例化它时都没有用使用向量。

这是因为需要单独编译,并且模板是实例化样式的多态性。

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

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

独立汇编的手段,我应该能够从bar.cpp独立编译Foo.cpp 中 。编译器完全独立地在每个编译单元上进行分析,优化和代码生成的所有艰苦工作。我们不需要进行整个程序分析。只是链接程序需要立即处理整个程序,因此链接程序的工作实际上要容易得多。

当我编译foo.cpp 时bar.cpp甚至不需要存在,但我仍然应该可以将foo.obar.o一起使用。o我刚刚生成的文件,而无需重新编译foo .cpp 。甚至可以将foo.cpp编译成动态库,而无需foo.cpp即可将分发到其他位置 ,并与在我编写foo.cpp之后多年编写的代码链接。

“实例化样式多态性” 意味着模板MyClass<T>并不是真正的泛型类,可以将其编译为可用于任何T值的代码。这将增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等。C++ 模板的目的是避免不得不编写几乎相同的class MyClass_intclass MyClass_float等,但最终仍然可以编译后的代码,就像我们分别编写每个版本一样。因此,模板实际上是模板。类模板不是类,而是为我们遇到的每个T创建一个新类的秘诀。模板不能编译为代码,只能将实例化模板的结果编译为代码。

因此,在编译foo.cpp时,编译器看不到bar.cpp来知道需要MyClass<int> 。它可以看到模板MyClass<T> ,但不能为此发出代码(它是模板,而不是类)。并且在编译bar.cpp时,编译器可以看到它需要创建MyClass<int> ,但是看不到模板MyClass<T> (仅在foo.h 中具有其接口),因此无法创建它。

如果Foo.cpp 中本身使用MyClass<int> ,将在编译Foo.cpp 中生成则该码,因此,当文件 bar.o被链接到它们foo.o 的可挂接和将工作。我们可以利用这一事实,通过编写单个模板,在. cpp 文件中实现一组有限的模板实例化。但是, bar.cpp无法将模板用作模板 ,并在所需的任何类型上实例化它。它只能使用foo.cpp的作者认为提供的模板化类的现有版本。

您可能会认为,在编译模板时,编译器应 “生成所有版本”,并且在链接过程中会滤除从未使用过的版本。除了庞大的开销和极端的困难之外,这种方法还会面临困难,因为指针和数组之类的 “类型修饰符” 功能甚至允许内置类型也可以产生无限数量的类型,当我现在扩展程序时会发生什么通过添加:

  • 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将需要模板的实例,更不用说是哪个具体实例了。对于更多的头文件和源文件,情况可能很快变得更加复杂。

可以争论的是,可以使编译器变得更聪明,以 “预见” 模板的所有用途,但是我敢肯定,创建递归或其他复杂的场景并不困难。 AFAIK,编译器不会提前这样做。正如 Anton 所指出的,一些编译器支持模板实例的显式导出声明,但并非所有编译器都支持(实例?)。

实际上,在 C ++ 11 之前,该标准定义了export关键字, 使得可以在头文件中声明模板并在其他地方实现它们。

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

结果,ISO C ++ 标准委员会决定删除使用 C ++ 11 的模板的export功能。

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

有一个export关键字可以缓解此问题,但是它几乎没有可移植性。

必须在标头中使用模板,因为编译器需要根据给定 / 推导的模板参数实例化不同版本的代码。请记住,模板并不直接代表代码,而是代表该代码的多个版本的模板。当您在.cpp文件中编译一个非模板函数时,您正在编译一个具体的函数 / 类。模板不是这种情况,可以用不同的类型实例化模板,即,用具体类型替换模板参数时必须发出具体代码。

带有export关键字的功能旨在用于单独的编译。 export功能在C++11和 AFAIK 中已被弃用,只有一个编译器实现了该功能。您不应该利用export 。在C++C++11不可能进行单独的编译,但是在C++17中可能无法进行单独的编译,如果有概念的话,我们可以采用某种方式进行单独的编译。

为了实现单独的编译,必须可以进行单独的模板主体检查。似乎可以用概念来解决。看一下最近在标准委员会会议上发表的这篇论文 。我认为这不是唯一的要求,因为您仍然需要为用户代码中的模板代码实例化代码。

模板的单独编译问题我想这也是当前正在工作的向模块迁移的问题。

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

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

尽管上面有很多很好的解释,但我仍然缺少将模板分为标题和正文的实用方法。
我主要关心的是在更改模板定义时避免重新编译所有模板用户。
对我而言,在模板主体中包含所有模板实例化不是一个可行的解决方案,因为模板创建者可能不了解其用法,并且模板用户可能无权对其进行修改。
我采用了以下方法,该方法也适用于较早的编译器(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 模块的一部分而产生的额外的编译时间和二进制大小膨胀,在许多情况下,您可以做的是使模板类从非模板化的基类继承而来。接口的非类型相关部分,该基类可以在. cpp 文件中实现。