2

编译器说当我这样做时它找不到函数的引用:

// link.h
template <class T>
    T *Link(T *&, T *(*)())

// link.cpp
template <class T>
T c:Link(T *&ChildNodeReference, T *(*ObjectCreator)()){

}

如果我在标题的类内部实现它会顺利进行。

拜托,我会在标题上工作,直到有人启发我。

C++ 中有一些奇怪的东西令人讨厌。我知道,这是有原因的等等。即便如此,编译器不能帮助你解决这个问题-_-”

4

5 回答 5

6

模板本质上是半类型安全的宏,因此存在限制。

普通(非模板)函数可以编译为驻留在对象/库文件中的本机代码,然后仅使用标头中可用的原型进行引用,这仅仅是因为这种函数只有一个版本。

使用模板,C++ 编译器必须分别编译函数的每个实例。显然,它不能“提前”完成,因为您可以为其实例化函数的类型集实际上是无限的(您始终可以在调用函数之前在代码中定义新类型)。事实上,同一个函数模板的两个实例化可以完全不同。考虑这种极端情况:

struct t1 {
  template <int>
  struct a {};
};

struct t2 {
  enum { a = 123 }; 
};

enum { b = 456, c = 789 };

template <class T>
void foo() {
   T::a<b>c;
}

现在如果我们调用foo<t1>(),它里面的语句是一个局部变量声明,因为t1::a是一个类模板:

T::a<b> c;

但是如果我们调用foo<t2>(),里面的语句是一个表达式,因为t2::a是一个整数常量:

(T::a < b) > c;

这只是为了表明编译器无法有意义地“编译”模板;它确实必须主要保留令牌。

综上所述,ISO C++ 确实提供了分离模板的声明和定义的能力,因此它们可以被视为普通函数,在 .h 文件中声明,在 .cpp 文件中定义。这称为“导出模板”,因为您必须在声明和定义之前加上关键字export

// link.h
export template <class T>
T *Link(T *&, T *(*)());

// link.cpp
export template <class T>
T *Link(T *&ChildNodeReference, T *(*ObjectCreator)()) {
}

然而,这是标准的一个有争议的特性,因为实施的负担非常高,并且大多数流行的实施拒绝实施它;值得注意的是,g++、MSVC 和 C++Builder 没有实现它。我所知道的唯一支持它的编译器是Comeau C++

于 2009-10-06T23:01:23.130 回答
5

在标头中编写非模板代码或非内联函数是一件坏事™。您拥有 cpp 文件的原因是为了防止多次重新定义相同的功能代码,等等。

与模板的不同之处在于,编译器实际上不会触及它们,直到您的代码实例化该模板的特化,这就是为什么它们需要将源代码放在标头中的原因。

当编译器找到模板特化的实例化(例如List<int>)时,它会返回包含的模板代码并使用该特化对其进行编译,这就是为什么您不会遇到重新定义函数代码的问题。

您似乎不明白的是,这不适用于非模板代码。所有非模板代码都正常编译,因此需要 CPP 文件只定义一次代码,然后将它们链接在一起。

如果您在标头中定义函数,则链接器将不会链接已编译的翻译单元,因为它们已多次定义相同的函数。

于 2009-10-06T22:53:26.100 回答
4

模板化实现(不仅仅是定义)必须在编译时可用。

因此,完整的模板代码通常放在头文件中。

于 2009-10-06T22:30:08.440 回答
0

模板代码必须在标题中。对不起,我完全错过了!(我以为我多年来一直在编写 C++ 代码:P)

于 2009-10-06T22:30:32.863 回答
0

您忘记了返回类型的 *。所以实现与定义不匹配。添加它,它应该可以工作:

T *c:Link(T *&ChildNodeReference, T *(*ObjectCreator)())
{
}

实现也必须在类定义下的头文件中,以便在编译时可用。

于 2009-10-06T22:40:47.927 回答