引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
当前回答
尽管标准C++没有这样的要求,但一些编译器要求所有函数和类模板都必须在使用的每个翻译单元中可用。实际上,对于这些编译器,模板函数的主体必须在头文件中可用。重复:这意味着这些编译器不允许在.cpp文件等非头文件中定义它们
有一个导出关键字可以缓解这个问题,但它离可移植性还很远。
其他回答
实际上,在C++11之前,该标准定义了export关键字,它可以在头文件中声明模板并在其他地方实现它们。在某种程度上讲。并非如此,正如唯一实现过该功能的人所指出的:
幻影优势#1:隐藏源代码。许多用户表示,他们希望通过使用导出不再需要为类的成员/非成员函数模板和成员函数提供定义模板。这不是真的。在导出的情况下,库编写者仍然必须提供完整的模板源代码或其直接因为实例化需要完整的信息。[...]
幻影优势#2:快速构建,减少依赖性。许多用户希望导出将允许真正的分离将模板编译为目标代码,他们希望这样可以加快构建速度。这不是因为导出模板的编译确实是独立的,但与目标代码无关。相反,出口几乎总是构建速度较慢,因为至少在预链接时仍必须完成相同数量的编译工作。出口甚至不减少模板定义之间的依赖性,因为依赖性是内在的,独立于文件组织。
没有一个流行的编译器实现了这个关键字。该功能的唯一实现是由Edison Design Group编写的前端,由Comeau C++编译器使用。所有其他人都要求您在头文件中编写模板,因为编译器需要模板定义来进行适当的实例化(正如其他人已经指出的那样)。
因此,ISO C++标准委员会决定删除C++11模板的导出功能。
模板通常用于标头中,因为编译器需要实例化不同版本的代码,这取决于为模板参数给定/推导的参数,并且(作为程序员)更容易让编译器多次重新编译同一代码,然后进行重复数据消除。请记住,模板并不直接表示代码,而是该代码的多个版本的模板。当您在.cpp文件中编译非模板函数时,您正在编译一个具体的函数/类。模板的情况并非如此,它可以用不同的类型实例化,也就是说,当用具体类型替换模板参数时,必须发出具体的代码。
export关键字有一个功能,用于单独编译。导出功能在C++11中被弃用,而且,AFAIK中只有一个编译器实现了它。你不应该利用出口。在C++或C++11中,单独编译是不可能的,但在C++17中,如果概念允许,我们可以有一些单独编译的方法。
为了实现单独的编译,必须可以进行单独的模板体检查。似乎可以用概念来解决问题。看看最近发表在标准委员会会议。我认为这不是唯一的要求,因为您仍然需要为用户代码中的模板代码实例化代码。
模板的单独编译问题我想也是迁移到模块时出现的问题,目前正在进行中。
编辑:截至2020年8月,模块已经成为C++的现实:https://en.cppreference.com/w/cpp/language/modules
只是在这里添加一些值得注意的内容。当模板类的方法不是函数模板时,可以在实现文件中定义它们。
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;
...
}
当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.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文件,并在其他源中找到它们时切换类型。
附带说明一下,当为模板类进行专门化时,可以将头与实现分开,因为根据定义,专门化意味着我专门化了一个可以单独编译和链接的具体类型。
这意味着定义模板类的方法实现的最可移植的方式是在模板类定义中定义它们。
template < typename ... >
class MyClass
{
int myMethod()
{
// Not just declaration. Add method implementation here
}
};