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

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

这是为什么?

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


当前回答

当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.cpp文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为main.cpp中包含的.h文件没有实现YET。这些文件已准备好与另一个对象文件链接,该对象文件定义了模板的实现,因此您拥有完整的a.out可执行文件。

然而,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例化生成代码,因此简单地编译一个与它的头文件分离的模板是行不通的,因为它们总是并行的,因为每个模板实例化都是一个全新的类。在常规类中,您可以将.h和.cpp分开,因为.h是该类的蓝图,.cpp是原始实现,因此任何实现文件都可以定期编译和链接,但是使用模板.h是类外观的蓝图,而不是对象外观,这意味着模板.cpp文件不是类的原始常规实现,它只是一个类的蓝图,因此任何.h模板文件的实现都无法编译,因为您需要一些具体的东西来编译,在这个意义上,模板是抽象的。

因此,模板永远不会单独编译,只在其他源文件中有具体实例化的地方编译。然而,具体的实例化需要知道模板文件的实现,因为简单地使用.h文件中的具体类型修改类型名T并不能完成任务,因为.cpp要链接什么,我以后找不到它,因为记住模板是抽象的,无法编译,所以我现在不得不给出实现,这样我就知道要编译和链接什么,现在我有了实现,它就链接到了封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道该类在使用我提供的类型时应该是什么样子,我就不能这样做,除非我通知编译器模板实现,所以现在编译器可以用我的类型替换t,并创建一个可以编译和链接的具体类。

总之,模板是类外观的蓝图,类是对象外观的蓝图。我不能将模板与它们的具体实例化分开编译,因为编译器只编译具体类型,换句话说,至少在C++中,模板是纯语言抽象的。可以说,我们必须对模板进行去抽象,我们通过给它们一个具体的类型来处理,这样我们的模板抽象就可以转换为一个常规的类文件,进而可以正常编译。分隔template.h文件和template.cpp文件是没有意义的。这是没有意义的,因为.cpp和.h的分离仅限于.cpp可以单独编译和单独链接的地方,因为我们不能单独编译它们,因为模板是一个抽象,因此,我们总是被迫将抽象与具体实例化放在一起,其中具体实例化总是必须知道所使用的类型。

这意味着typename T get在编译步骤而不是链接步骤中被替换,所以如果我试图编译一个模板而不将T替换为一个对编译器来说完全没有意义的具体值类型,结果无法创建对象代码,因为它不知道T是什么。

从技术上讲,可以创建某种功能来保存template.cpp文件,并在其他源中找到它们时切换类型。

附带说明一下,当为模板类进行专门化时,可以将头与实现分开,因为根据定义,专门化意味着我专门化了一个可以单独编译和链接的具体类型。

其他回答

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

有一个导出关键字可以缓解这个问题,但它离可移植性还很远。

实际上,在C++11之前,该标准定义了export关键字,它可以在头文件中声明模板并在其他地方实现它们。在某种程度上讲。并非如此,正如唯一实现过该功能的人所指出的:

幻影优势#1:隐藏源代码。许多用户表示,他们希望通过使用导出不再需要为类的成员/非成员函数模板和成员函数提供定义模板。这不是真的。在导出的情况下,库编写者仍然必须提供完整的模板源代码或其直接因为实例化需要完整的信息。[...]

幻影优势#2:快速构建,减少依赖性。许多用户希望导出将允许真正的分离将模板编译为目标代码,他们希望这样可以加快构建速度。这不是因为导出模板的编译确实是独立的,但与目标代码无关。相反,出口几乎总是构建速度较慢,因为至少在预链接时仍必须完成相同数量的编译工作。出口甚至不减少模板定义之间的依赖性,因为依赖性是内在的,独立于文件组织。

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

因此,ISO C++标准委员会决定删除C++11模板的导出功能。

当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.cpp文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为main.cpp中包含的.h文件没有实现YET。这些文件已准备好与另一个对象文件链接,该对象文件定义了模板的实现,因此您拥有完整的a.out可执行文件。

然而,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例化生成代码,因此简单地编译一个与它的头文件分离的模板是行不通的,因为它们总是并行的,因为每个模板实例化都是一个全新的类。在常规类中,您可以将.h和.cpp分开,因为.h是该类的蓝图,.cpp是原始实现,因此任何实现文件都可以定期编译和链接,但是使用模板.h是类外观的蓝图,而不是对象外观,这意味着模板.cpp文件不是类的原始常规实现,它只是一个类的蓝图,因此任何.h模板文件的实现都无法编译,因为您需要一些具体的东西来编译,在这个意义上,模板是抽象的。

因此,模板永远不会单独编译,只在其他源文件中有具体实例化的地方编译。然而,具体的实例化需要知道模板文件的实现,因为简单地使用.h文件中的具体类型修改类型名T并不能完成任务,因为.cpp要链接什么,我以后找不到它,因为记住模板是抽象的,无法编译,所以我现在不得不给出实现,这样我就知道要编译和链接什么,现在我有了实现,它就链接到了封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道该类在使用我提供的类型时应该是什么样子,我就不能这样做,除非我通知编译器模板实现,所以现在编译器可以用我的类型替换t,并创建一个可以编译和链接的具体类。

总之,模板是类外观的蓝图,类是对象外观的蓝图。我不能将模板与它们的具体实例化分开编译,因为编译器只编译具体类型,换句话说,至少在C++中,模板是纯语言抽象的。可以说,我们必须对模板进行去抽象,我们通过给它们一个具体的类型来处理,这样我们的模板抽象就可以转换为一个常规的类文件,进而可以正常编译。分隔template.h文件和template.cpp文件是没有意义的。这是没有意义的,因为.cpp和.h的分离仅限于.cpp可以单独编译和单独链接的地方,因为我们不能单独编译它们,因为模板是一个抽象,因此,我们总是被迫将抽象与具体实例化放在一起,其中具体实例化总是必须知道所使用的类型。

这意味着typename T get在编译步骤而不是链接步骤中被替换,所以如果我试图编译一个模板而不将T替换为一个对编译器来说完全没有意义的具体值类型,结果无法创建对象代码,因为它不知道T是什么。

从技术上讲,可以创建某种功能来保存template.cpp文件,并在其他源中找到它们时切换类型。

附带说明一下,当为模板类进行专门化时,可以将头与实现分开,因为根据定义,专门化意味着我专门化了一个可以单独编译和链接的具体类型。

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

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

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