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

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

这是为什么?

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


当前回答

这是完全正确的,因为编译器必须知道它的分配类型。模板类、函数、枚举等。。如果要将头文件公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像c/cpp文件那样编译。如果编译器不知道该类型,则无法编译它。在.Net中,它可以编译,因为所有对象都派生自Object类。这不是.Net。

其他回答

这里有很多正确的答案,但我想补充一下(为了完整):

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

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

template class vector<int>;

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

上面的例子是非常无用的,因为vector是在头文件中完全定义的,除非公共include文件(预编译头文件?)使用外部模板类vector<int>,以防止它在所有其他使用vector的(1000?)文件中实例化。

这是完全正确的,因为编译器必须知道它的分配类型。模板类、函数、枚举等。。如果要将头文件公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像c/cpp文件那样编译。如果编译器不知道该类型,则无法编译它。在.Net中,它可以编译,因为所有对象都派生自Object类。这不是.Net。

只是在这里添加一些值得注意的内容。当模板类的方法不是函数模板时,可以在实现文件中定义它们。


myQueue.hpp:

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

    bool isEmpty();

    ...
}    

myQueue.cpp:

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


main()
{
    QueueA<char> Q;

    ...
}

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

有人会说,编译器可以更聪明地“前瞻”模板的所有用途,但我确信创建递归或其他复杂场景并不困难。顺便说一句,编译器不会再这样做了。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(现在?)。

单独实现的方法如下。

内部_内部.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
}

小时

#include <foo.tpp>

主.cpp

#include <foo.h>

inner_fo.h具有前向声明。foo.tpp具有实现并包括inner_fo.h;而foo.h只有一行,包括foo.tpp。

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

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