引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
当前回答
这里有很多正确的答案,但我想补充一下(为了完整):
如果在实现cpp文件的底部,对模板将使用的所有类型进行显式实例化,链接器将能够像往常一样找到它们。
编辑:添加显式模板实例化示例。在定义模板并定义所有成员函数后使用。
template class vector<int>;
这将实例化(从而使链接器可用)类及其所有成员函数(仅限)。类似的语法适用于函数模板,因此如果您有非成员运算符重载,则可能需要对这些重载执行相同的操作。
上面的例子是非常无用的,因为vector是在头文件中完全定义的,除非公共include文件(预编译头文件?)使用外部模板类vector<int>,以防止它在所有其他使用vector的(1000?)文件中实例化。
其他回答
尽管上面有很多很好的解释,但我缺少一种将模板分离为页眉和正文的实用方法。
我主要担心的是,当我更改其定义时,避免重新编译所有模板用户。
对我来说,在模板主体中包含所有模板实例不是一个可行的解决方案,因为模板作者可能不知道其使用情况,模板用户可能无权修改它。
我采用了以下方法,这也适用于较旧的编译器(gcc 4.3.4,aCC A.03.13)。
对于每个模板的使用,在其自己的头文件(从UML模型生成)中都有一个typedef。它的主体包含实例化(最终在一个库中,该库在末尾链接)。
模板的每个用户都包含该头文件并使用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
我的模板.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
我的实例化模板.cpp:
#include "MyTemplate.cpp"
template class MyTemplate< int >;
main.cpp:
#include "MyInstantiatedTemplate.h"
int main()
{
MyInstantiatedTemplate m(100);
m.dump();
return 0;
}
这样,只需要重新编译模板实例,而不是所有模板用户(和依赖项)。
注意:没有必要将实现放在头文件中,请参阅答案末尾的替代解决方案。
无论如何,代码失败的原因是,在实例化模板时,编译器使用给定的模板参数创建一个新类。例如:
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)中实现类,并在头的末尾包含该实现文件。
食品h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
食品.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
这样,实现仍然与声明分离,但编译器可以访问。
替代解决方案
另一个解决方案是保持实现分离,并显式实例化您需要的所有模板实例:
食品h
// no implementation
template <typename T> struct 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++超级常见问题解答。
模板通常用于标头中,因为编译器需要实例化不同版本的代码,这取决于为模板参数给定/推导的参数,并且(作为程序员)更容易让编译器多次重新编译同一代码,然后进行重复数据消除。请记住,模板并不直接表示代码,而是该代码的多个版本的模板。当您在.cpp文件中编译非模板函数时,您正在编译一个具体的函数/类。模板的情况并非如此,它可以用不同的类型实例化,也就是说,当用具体类型替换模板参数时,必须发出具体的代码。
export关键字有一个功能,用于单独编译。导出功能在C++11中被弃用,而且,AFAIK中只有一个编译器实现了它。你不应该利用出口。在C++或C++11中,单独编译是不可能的,但在C++17中,如果概念允许,我们可以有一些单独编译的方法。
为了实现单独的编译,必须可以进行单独的模板体检查。似乎可以用概念来解决问题。看看最近发表在标准委员会会议。我认为这不是唯一的要求,因为您仍然需要为用户代码中的模板代码实例化代码。
模板的单独编译问题我想也是迁移到模块时出现的问题,目前正在进行中。
编辑:截至2020年8月,模块已经成为C++的现实:https://en.cppreference.com/w/cpp/language/modules
在实际将模板编译成目标代码之前,需要由编译器实例化模板。只有当模板参数已知时,才能实现此实例化。现在想象一个场景,其中模板函数在a.h中声明,在a.pp中定义,并在b.cpp中使用。编译a.pp时,不一定知道即将到来的编译b.cpp将需要模板的实例,更不用说是哪个特定的实例。对于更多的头文件和源文件,情况可能会很快变得更加复杂。
有人会说,编译器可以更聪明地“前瞻”模板的所有用途,但我确信创建递归或其他复杂场景并不困难。顺便说一句,编译器不会再这样做了。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(现在?)。
实际上,在C++11之前,该标准定义了export关键字,它可以在头文件中声明模板并在其他地方实现它们。在某种程度上讲。并非如此,正如唯一实现过该功能的人所指出的:
幻影优势#1:隐藏源代码。许多用户表示,他们希望通过使用导出不再需要为类的成员/非成员函数模板和成员函数提供定义模板。这不是真的。在导出的情况下,库编写者仍然必须提供完整的模板源代码或其直接因为实例化需要完整的信息。[...]
幻影优势#2:快速构建,减少依赖性。许多用户希望导出将允许真正的分离将模板编译为目标代码,他们希望这样可以加快构建速度。这不是因为导出模板的编译确实是独立的,但与目标代码无关。相反,出口几乎总是构建速度较慢,因为至少在预链接时仍必须完成相同数量的编译工作。出口甚至不减少模板定义之间的依赖性,因为依赖性是内在的,独立于文件组织。
没有一个流行的编译器实现了这个关键字。该功能的唯一实现是由Edison Design Group编写的前端,由Comeau C++编译器使用。所有其他人都要求您在头文件中编写模板,因为编译器需要模板定义来进行适当的实例化(正如其他人已经指出的那样)。
因此,ISO C++标准委员会决定删除C++11模板的导出功能。