提问者:小点点

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


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

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

这是为什么?

(澄清:头文件不是唯一的可移植解决方案。但它是最方便的可移植解决方案。)


共3个答案

匿名用户

警告:没有必要将实现放在头文件中,请参见本答案末尾的替代解决方案。

总之,代码失败的原因是,当实例化模板时,编译器使用给定的模板参数创建了一个新类。 例如:

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++超级FAQ。

匿名用户

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

让我们更接近混凝土来解释一下。 假设我有以下文件:

  • foo.h
    • 声明类MyClass
    • 的接口
    • 定义类MyClass
    • 的实现
    • 使用MyClass

    单独编译意味着我应该能够独立于bar.cpp编译foo.cpp。 编译器完全独立地在每个编译单元上完成分析,优化和代码生成的所有艰苦工作; 我们不需要做整个程序分析。 只有链接器需要一次处理整个程序,而且链接器的工作要简单得多。

    当我编译foo.cpp时,bar.cpp甚至不需要存在,但是我应该仍然能够将已经有的foo.o与刚刚生成的bar.o链接在一起,而不需要重新编译foo.cpp。 甚至可以将foo.cpp编译成一个动态库,分发到没有foo.cpp的其他地方,并与他们在我编写foo.cpp多年后编写的代码链接。

    “实例化风格的多态性”意味着模板MyClass实际上不是一个泛型类,可以编译成对t的任何值都有效的代码。 这会增加开销,例如装箱,需要将函数指针传递给分配器和构造器等。C++模板的目的是避免编写几乎相同的类MYCLASS_INT类MYCLASS_FLOAT等,但仍然能够以编译后的代码结束,就好像我们分别编写了每个版本一样。 所以模板就是字面上的模板; 类模板不是类,它是为我们遇到的每个T创建新类的秘诀。 模板无法编译成代码,只能编译实例化模板的结果。

    因此,当foo.cpp被编译时,编译器无法看到bar.cpp来知道需要MyClass。 它可以看到模板MyClass<;t>,但不能为此发出代码(它是模板,不是类)。 并且当bar.cpp被编译时,编译器可以看到它需要创建一个MyClass,但是它看不到模板MyClass(只有它在foo.h中的接口),所以它不能创建它。

    如果foo.cpp本身使用MyClass,那么在编译foo.cpp时将生成相应的代码,因此当bar.o链接到foo.o时,它们可以被挂起并正常工作。 我们可以利用这个事实,通过编写一个模板,允许在。cpp文件中实现一组有限的模板实例化。 但是bar.cpp无法将模板用作模板并在它喜欢的任何类型上实例化它; 它只能使用foo.cpp的作者认为应该提供的模板化类的预先存在版本。

    您可能会认为,在编译模板时,编译器应该“生成所有版本”,在链接过程中过滤掉从未使用过的版本。 除了由于指针和数组等“类型修饰符”特性允许内置类型产生无限数量的类型,这种方法将面临巨大的开销和极端的困难之外,当我现在通过添加以下内容来扩展我的程序时会发生什么:

    • baz.cpp
      • 声明和实现类BazPrivate,并使用MyClass

      这不可能行得通除非我们

        每次更改程序中的任何其他文件时,
      1. 都必须重新编译foo.cpp,以防它添加了一个新的MyClass
      2. 实例
      3. 要求baz.cpp包含(可能通过头包含)MyClass的完整模板,以便编译器在编译baz.cpp期间生成MyClass

      没有人喜欢(1),因为整个程序分析编译系统需要花费很长的时间来编译,而且因为它使得没有源代码就不可能分发编译过的库。 所以我们有(2)。

匿名用户

这里有很多正确答案,但我想补充一点(为了完整):

如果您在实现cpp文件的底部显式地实例化模板将要使用的所有类型,那么链接器将能够像往常一样找到它们。

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

template class vector<int>;

这将实例化(从而使链接器可用)类及其所有成员函数(仅)。 类似的语法适用于模板函数,因此如果您有非成员运算符重载,您可能需要对这些重载执行相同的操作。

上面的例子是相当无用的,因为vector是在头中完全定义的,除非是一个公共的include文件(预编译的头?) 使用外部模板类向量,以防止它在所有其他(1000?)中实例化它 使用矢量的文件。