c - 为什么模板只可以在头文件中实现?

  显示原文与译文双语对照的内容

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

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

为什么是这样?

( 澄清:头文件不是 portable便携式解决方案) 。 但它们是最方便的便携解决方案。

时间:

因为在实例化模板时,编译器使用给定的模板参数创建一个新类。 例如:


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++ FaqLite 。

这里有很多正确的答案,但是我想添加这个( 用于完整性):

如果你在实现cpp文件的底部,做所有类型的显式实例化模板将使用,链接器将能够找到它们。

编辑:添加显式模板实例化示例。 在定义模板之后使用,并且所有成员函数都已经定义。


template class vector<int>;

这将实例化( 从而使链接器可以使用) 类及其所有成员函数( 仅仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限仅限而已而已) 。 类似的语法适用于模板功能,所以如果你有非会员运算符过载可能需要对那些做同样的事情。

上面的例子是相当无用的,因为向量完全定义在头,除非一个常见的包含文件( 预编译头) 用途? extern template class vector<int> 以防止它实例化它所有的其他 ( 1000) 文件,使用向量。

这是因为需要单独编译,因为模板是instantiation-style多态。

让我们更接近具体的具体解释。 假设我有以下文件:

  • foo.h
    • 声明 class MyClass<T>的接口
  • foo.cpp
    • 定义 class MyClass<T>的实现
  • bar.cpp
    • 使用 MyClass<int>

单独的编译意味着我应该能够编译 foo.cpp 独立于 bar.cpp 。 编译器完全独立地在每个编译单元上执行分析,优化和代码生成工作;我们不需要进行whole-program分析。 它只是需要同时处理整个程序的链接器,链接器的工作非常简单。

bar.cpp 甚至不需要存在当我编译 foo.cpp, 但我应该仍然能够链接 foo.o 我已经连同 bar.o 我刚刚产生,而不需要重新编译 foo.cppfoo.cpp 甚至可以编译成动态库,分布在其他地方没有 foo.cpp, 和与我写代码写年后 foo.cpp

"Instantiation-style多态"意味着模板 MyClass<T> 不是真正的泛型类,它可以编译为可以为任何 T 值工作的代码。 将性别overhead add 。needingto引领,allocatorspointers细胞pass 等等 constructors and tothe意图, C++ 表格is邻人拉大,将有近16至 class MyClass_int 等等), class MyClass_float cestill法典时,主要与已 731 we若as名册的每一版民政局统计。 模板是模板;一个类模板不是一个类,它是一个秘方为每个解决方案创建一个新类 T 我们相遇。 模板不能编译到代码中,只能编译模板实例化的结果。

所以当 foo.cpp 编译,编译器无法看到 bar.cpp 知道 MyClass<int> 是必要的。 它可以看到模板 MyClass<T>,但不能为该( 这是一个模板,不是一个类) 发出代码。 当 bar.cpp 编译,编译器可以看到它需要创建一个 MyClass<int>, 但不能看到模板 MyClass<T> ( 只有其接口 foo.h ) 所以不能创建它。

如果 foo.cpp 本身使用 MyClass<int>, 然后将生成的代码在编译 foo.cpp, 所以当 bar.ofoo.o 它们可以连接并将工作。 我们可以使用这个事实,通过编写一个模板来在. cpp 文件中实现一个有限的模板实例化。 但是没有办法 bar.cpp 作为模板使用模板并实例化它所喜欢的任何类型;它只能使用现有版本的模板类的作者 foo.cpp 认为提供。

你可能会认为编译模板时编译器应该"生成所有版本",而在链接过程中从未使用过的模板。 除了巨大的开销,这种方法将面临极端困难因为"类型修饰符"功能,如指针和数组允许甚至只是内置类型产生无限的类型,当我现在通过添加扩展我的程序:

  • baz.cpp
    • 声明并实现 class BazPrivate,并使用 MyClass<BazPrivate>

除非我们要么要么要么我们要么

  1. 我们必须重新编译 foo.cpp 每次改变中的任何其他文件程序,以防它添加了一个新小说 MyClass<T> 实例化
  2. 要求 baz.cpp 包含( 可能通过头包括) MyClass<T>的完整模板,因此编译器可以在编译过程中生成 MyClass<BazPrivate>baz.cpp

没有人喜欢( 1 ),因为whole-program-analysis编译系统使用 forever来编译,因为它使得编译编译的库不需要源代码。 所以我们有( 2 ) 。

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

可以认为,对于模板的所有用法,compiers可以变得更聪明,但我确信创建递归或者其他复杂的场景不会很困难。 AFAIK,compliers不做这样的外观。 王牌安东指出,由于一些编撰者out of tempate decarations支持出口,但不是所有instantiations编撰者支持it( 还还还) 。

模板必须在头文件中使用,因为编译器需要根据为模板参数指定/导出的参数实例化代码的不同版本。 记住,模板并不直接表示代码,而是一个模板的多个版本的模板。 当你编译 non-template .cpp 文件函数,编译一个具体的函数/类。 这不是模板的情况,可以用不同的类型实例化,即,当用具体类型替换模板参数时,必须发出具体的代码。

有一个带有 export 关键字的特性,用于单独编译。 export 特性在 C++11 中不推荐使用,并且,只有一个编译器实现了它。 你不应该使用 export 。 在 C++ 或者 C++11 中不可能进行单独的编译,但可能在 C++17 中,如果概念成功,我们可以使用一些单独的编译方法。

要实现单独的编译,必须有单独的模板正文检查。 似乎一个解决方案是有可能的。 看看这个最近commitee标准会议上提出。 我认为这不是惟一的需求,因为你仍然需要在用户代码中为模板代码实例化代码。

模板的单独编译问题我猜它也是一个问题,在迁移到模块的模块中,目前正在运行。

这意味着定义模板类方法实现的最可移植方式是在模板类定义中定义它们。


template <typename.. .> 
class MyClass
{

 int myMethod()
 {
//Not just declaration. Add method implementation here
 }
};

...