c++模板头文件




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

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

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

为什么是这样?

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


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

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

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


一种独立实施的方式如下。

//inner_foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};


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


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

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

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

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


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

如果您在实现cpp文件的底部对模板将使用的所有类型进行显式实例化,则链接器将能够像往常一样查找它们。

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

template class vector<int>;

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

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


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


尽管上面有很多很好的解释,但我缺少一种将模板分离为标题和正文的实用方法。
我主要关心的是在我更改其定义时避免重新编译所有模板用户。
在模板主体中拥有所有模板实例对我来说不是一个可行的解决方案,因为如果模板作者的使用和模板用户可能没有修改它的权利,模板作者可能不知道全部。
我采取了以下方法,这也适用于较老的编译器(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;
}

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


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

有一个导出关键字可以缓解这个问题,但是它远不是便携式的。


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

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


这意味着定义模板类的方法实现的最便捷方式是在模板类定义中定义它们。

template < typename ... >
class MyClass
{

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

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





c++-faq