为什么只能在头文件中实现模板?

来自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?)文件中实例化它时,矢量在头文件中完全定义那使用矢量。


这是因为需要单独编译,并且因为模板是实例化风格的多态。

让我们稍微接近具体的解释。 说我有以下文件:

  • foo.h中
  • 声明class MyClass<T>的接口
  • Foo.cpp中
  • 定义class MyClass<T>
  • bar.cpp
  • 使用MyClass<int>
  • 独立编译意味着我应该能够从bar.cpp独立编译Foo.cpp中 。 编译器完全独立地完成每个编译单元上的所有分析,优化和代码生成工作; 我们不需要做全程序分析。 只有链接器需要一次处理整个程序,并且链接器的工作要容易得多。

    bar.cpp甚至不需要存在,当我编译Foo.cpp中 ,但我仍然应该能够在foo.o的我已经与我刚刚产生的文件bar.o共进链接,而无需重新编译FOO .cppfoo.cpp甚至可以编译成一个动态库,在没有foo.cpp的地方分发到其他地方,并且可以在编写foo.cpp几年后写入代码。

    “实例化风格的多态性”意味着模板MyClass<T>并不是一个真正的泛型类,它可以编译为可以用于任何T值的代码。 这会增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等.C ++模板的目的是避免编写几乎相同的class MyClass_intclass 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的作者认为提供的模板化类的预先存在的版本。

    您可能认为编译模板时编译器应该“生成所有版本”,并且在链接过程中从未使用的模板被过滤掉。 除了巨大的开销以及这种方法面临的极端困难之外,因为像指针和数组这样的“类型修饰符”功能甚至只允许内置类型产生无限数量的类型,所以当我现在扩展我的程序时会发生什么通过增加:

  • baz.cpp
  • 声明并实现class BazPrivate ,并使用MyClass<BazPrivate>
  • 除非我们做到这一点,否则这种方法是行不通的

  • 每次我们改变程序中的任何其他文件时,必须重新编译foo.cpp ,以防添加MyClass<T>的新实例化。
  • 要求baz.cpp包含(可能通过头文件包含) 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?

    下一篇: std::wstring VS std::string