为什么只能在头文件中实现模板?
来自C ++标准库的引用:教程和手册:
目前使用模板的唯一便携方式是使用内联函数在头文件中实现它们。
为什么是这样?
(澄清:头文件不是唯一的便携式解决方案,但它们是最便捷的便携式解决方案。)
没有必要将实现放在头文件中,请参阅本答案末尾的替代解决方案。
无论如何,你的代码失败的原因是,当实例化一个模板时,编译器用给定的模板参数创建一个新类。 例如:
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)中实现该类,并将该实现文件包含在头的末尾。
// Foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
这样,实现仍然与声明分离,但编译器可以访问。
另一个解决方案是保持实现分离,并显式实例化所有需要的模板实例:
// Foo.h
// no implementation
template <typename T> struct Foo { ... };
//----------------------------------------
// 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 ++ Super-FAQ。
在这里很正确的答案,但我想补充(完整性):
如果您在实现cpp文件的底部对模板将要使用的所有类型进行显式实例化,那么链接器将能够像平常一样查找它们。
编辑:添加显式模板实例的示例。 在定义模板之后使用,并且所有成员函数都已定义。
template class vector<int>;
这将实例化(从而使链接器可用)该类及其所有成员函数(仅)。 类似的语法适用于模板函数,所以如果你有非成员运算符重载,你可能需要为这些重载做同样的事情。
上面的例子是相当无用的,因为除了当一个普通的包含文件(预编译头文件?)使用extern template class vector<int>
以防止其在所有其他(1000?)文件中实例化它时,矢量在头文件中完全定义那使用矢量。
这是因为需要单独编译,并且因为模板是实例化风格的多态。
让我们稍微接近具体的解释。 说我有以下文件:
class MyClass<T>
的接口 class MyClass<T>
MyClass<int>
独立编译意味着我应该能够从bar.cpp独立编译Foo.cpp中 。 编译器完全独立地完成每个编译单元上的所有分析,优化和代码生成工作; 我们不需要做全程序分析。 只有链接器需要一次处理整个程序,并且链接器的工作要容易得多。
bar.cpp甚至不需要存在,当我编译Foo.cpp中 ,但我仍然应该能够在foo.o的我已经与我刚刚产生的文件bar.o共进链接,而无需重新编译FOO .cpp 。 foo.cpp甚至可以编译成一个动态库,在没有foo.cpp的地方分发到其他地方,并且可以在编写foo.cpp几年后写入代码。
“实例化风格的多态性”意味着模板MyClass<T>
并不是一个真正的泛型类,它可以编译为可以用于任何T
值的代码。 这会增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等.C ++模板的目的是避免编写几乎相同的class MyClass_int
, class 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的作者认为提供的模板化类的预先存在的版本。
您可能认为编译模板时编译器应该“生成所有版本”,并且在链接过程中从未使用的模板被过滤掉。 除了巨大的开销以及这种方法面临的极端困难之外,因为像指针和数组这样的“类型修饰符”功能甚至只允许内置类型产生无限数量的类型,所以当我现在扩展我的程序时会发生什么通过增加:
class BazPrivate
,并使用MyClass<BazPrivate>
除非我们做到这一点,否则这种方法是行不通的
MyClass<T>
的新实例化。 MyClass<T>
的完整模板,以便编译器在编译baz.cpp期间可以生成MyClass<BazPrivate>
。 没有人喜欢(1),因为整个程序分析编译系统需要永远编译,并且因为它不可能在没有源代码的情况下分发已编译的库。 所以我们有(2)。
链接地址: http://www.djcxy.com/p/5057.html上一篇: Why can templates only be implemented in the header file?