引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
当前回答
在实际将模板编译成目标代码之前,需要由编译器实例化模板。只有当模板参数已知时,才能实现此实例化。现在想象一个场景,其中模板函数在a.h中声明,在a.pp中定义,并在b.cpp中使用。编译a.pp时,不一定知道即将到来的编译b.cpp将需要模板的实例,更不用说是哪个特定的实例。对于更多的头文件和源文件,情况可能会很快变得更加复杂。
有人会说,编译器可以更聪明地“前瞻”模板的所有用途,但我确信创建递归或其他复杂场景并不困难。顺便说一句,编译器不会再这样做了。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(现在?)。
其他回答
这是因为需要单独编译,并且模板是实例化样式多态性。
让我们更接近具体的解释。假设我有以下文件:
小时声明类MyClass<T>的接口foo.cpp定义类MyClass<T>的实现棒.cpp使用MyClass<int>
独立编译意味着我应该能够独立于bar.cpp编译foo.cpp。编译器完全独立地在每个编译单元上完成分析、优化和代码生成的所有艰苦工作;我们不需要做整个程序分析。只有链接器需要一次处理整个程序,而且链接器的工作要简单得多。
当我编译foo.cpp时,bar.cpp甚至不需要存在,但我应该仍然能够将我已经拥有的foo.o与我刚刚制作的bar.o链接在一起,而不需要重新编译foo.cpp。foo.cpp甚至可以被编译成一个动态库,在没有foo.cpp的情况下分发到其他地方,并与他们在我编写foo.cpp多年后编写的代码链接。
“实例化样式多态性”意味着模板MyClass<T>实际上不是一个通用类,它可以编译为可用于任何T值的代码。这会增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等。C++模板的目的是避免编写几乎相同的类MyClass_int、类MyClass_float等,但最终仍然能够编写出编译后的代码,就像我们分别编写了每个版本一样。因此,模板实际上就是模板;类模板不是类,它是为我们遇到的每个T创建新类的方法。模板不能编译成代码,只能编译实例化模板的结果。
因此,当编译foo.cpp时,编译器无法看到bar.cpp以知道需要MyClass<int>。它可以看到模板MyClass<T>,但不能为此发出代码(它是一个模板,而不是类)。编译bar.cpp时,编译器可以看到它需要创建MyClass<int>,但它看不到模板MyClass<t>(只有foo.h中的接口),因此无法创建它。
如果foo.cpp本身使用MyClass<int>,那么编译foo.cpp时将生成该代码,因此当bar.o链接到foo.o时,它们可以连接起来并工作。我们可以使用这个事实,通过编写一个模板,在.cpp文件中实现一组有限的模板实例化。但bar.cpp无法将模板用作模板,并在它喜欢的任何类型上实例化它;它只能使用foo.cpp作者认为可以提供的模板类的预先存在的版本。
您可能会认为,在编译模板时,编译器应该“生成所有版本”,在链接过程中过滤掉从未使用过的版本。除了由于指针和数组等“类型修改器”特性,即使是内置类型也可以产生无限数量的类型,因此这种方法将面临巨大的开销和极端困难外,现在我通过添加以下内容来扩展程序时会发生什么:
巴兹卡普声明并实现类BazPrivate,并使用MyClass<BazPrivate>
除非我们
每次我们更改程序中的任何其他文件时,都必须重新编译foo.cpp,以防它添加了MyClass的新实例<T>要求baz.cpp包含(可能通过headerincludes)MyClass<T>的完整模板,以便编译器可以在编译baz.cpp期间生成MyClass<BazPrivate>。
没有人喜欢(1),因为整个程序分析编译系统需要很长时间才能编译,而且如果没有源代码,就无法分发编译后的库。所以我们有(2)。
在实际将模板编译成目标代码之前,需要由编译器实例化模板。只有当模板参数已知时,才能实现此实例化。现在想象一个场景,其中模板函数在a.h中声明,在a.pp中定义,并在b.cpp中使用。编译a.pp时,不一定知道即将到来的编译b.cpp将需要模板的实例,更不用说是哪个特定的实例。对于更多的头文件和源文件,情况可能会很快变得更加复杂。
有人会说,编译器可以更聪明地“前瞻”模板的所有用途,但我确信创建递归或其他复杂场景并不困难。顺便说一句,编译器不会再这样做了。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(现在?)。
只是在这里添加一些值得注意的内容。当模板类的方法不是函数模板时,可以在实现文件中定义它们。
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;
...
}
尽管上面有很多很好的解释,但我缺少一种将模板分离为页眉和正文的实用方法。
我主要担心的是,当我更改其定义时,避免重新编译所有模板用户。
对我来说,在模板主体中包含所有模板实例不是一个可行的解决方案,因为模板作者可能不知道其使用情况,模板用户可能无权修改它。
我采用了以下方法,这也适用于较旧的编译器(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;
}
这样,只需要重新编译模板实例,而不是所有模板用户(和依赖项)。
尽管标准C++没有这样的要求,但一些编译器要求所有函数和类模板都必须在使用的每个翻译单元中可用。实际上,对于这些编译器,模板函数的主体必须在头文件中可用。重复:这意味着这些编译器不允许在.cpp文件等非头文件中定义它们
有一个导出关键字可以缓解这个问题,但它离可移植性还很远。