3

考虑以下文件:

Foo.H

template <typename T>
struct Foo
{
  int foo();
};

template <typename T>
int Foo<T>::foo()
{
  return 6;
}

Foo.C

#include "Foo.H"

template <>
int Foo<int>::foo()
{
  return 7;
}

主程序

#include <iostream>
#include "Foo.H"

using namespace std;

int main()
{
  Foo<int> f;
  cout << f.foo() << endl;
  return 0;
}

当我编译并运行时,会打印 7。这里发生了什么?模板何时实例化?如果编译器这样做了,编译器怎么知道不实例化自己的 Foo 版本?

4

4 回答 4

17

问题是您违反了单一定义规则。在 main.C 中,您包含了 Foo.H 但没有包含 Foo.C(这是有道理的,因为它是一个源文件)。编译 main.C 时,编译器不知道您已在 Foo.C 中专门化了模板,因此它使用通用版本(返回 6)并编译 Foo 类。然后当它编译 Foo.C 时,它会看到一个可以立即编译的完整特化——它不需要等待它在某处被实例化,因为所有类型都已填写(如果你有两个模板参数并且只有专门的,但情况并非如此),它编译了一个新的和不同的Foo 类。

通常,同一事物的多个定义会导致链接器错误。但是模板实例化是“弱符号”,这意味着允许多个定义。链接器假定所有定义都完全相同,然后随机选择一个(嗯,可能始终是第一个或最后一个,但只是作为实现的巧合)。

为什么要让它们成为弱符号?因为 Foo 可能在多个源文件中使用,每个源文件都单独编译,并且每次在编译单元中使用 Foo 时都会生成一个新的实例化。通常,这些都是多余的,因此丢弃它们是有意义的。但是您违反了这一假设,在一个编译单元 (foo.C) 中提供了专门化,但在另一个编译单元 (main.C) 中没有提供专门化。

如果您在 Foo.H 中声明模板特化,那么在编译 main.C 时它不会生成 Foo 的实例化,从而确保您的程序中只存在一个定义。

于 2010-01-13T16:22:26.563 回答
2

我猜编译器会实例化 Foo,但是在链接它时会选择你专门的 Foo。

于 2010-01-13T16:19:00.970 回答
1

编译 main.c 时,编译器不知道您的专业化。Foo<int>::foo()我猜它必须基于非专业模板生成自己的版本。

但是,在链接时,链接器会看到Foo<int>::foo()存在一个特化 for。因此,它将专用版本放入可执行文件中。

最后,即使在编译时不知道,main.c 也会调用Foo<int>::foo().

于 2010-01-13T16:24:17.357 回答
-2

模板为模板参数的每种组合生成不同的类。这发生在编译时,这就是模板应该驻留在头文件中的原因。您对 int 参数进行了专门化,编译器会调用Foo<int>::foo()您的变量f。这就像在编译时重写虚函数。

于 2010-01-13T16:11:15.183 回答