对模板类构造函数“ ”的引用未定义

我不知道为什么会发生这种情况,因为我认为我已经正确地声明和定义了一切。

我有以下程序,用模板设计。这是一个队列的简单实现,成员函数为";add";,";substract"和";打印";。

我已经在Fine";中为队列定义了节点nodo_colaypila.H:

#ifndef NODO_COLAYPILA_H
#define NODO_COLAYPILA_H


#include <iostream>


template <class T> class cola;


template <class T> class nodo_colaypila
{
T elem;
nodo_colaypila<T>* sig;
friend class cola<T>;
public:
nodo_colaypila(T, nodo_colaypila<T>*);


};

然后在";中实现nodo_colaypila.CPP";

#include "nodo_colaypila.h"
#include <iostream>


template <class T> nodo_colaypila<T>::nodo_colaypila(T a, nodo_colaypila<T>* siguiente = NULL)
{
elem = a;
sig = siguiente;//ctor
}

然后,队列模板类及其函数的定义和声明:

";可乐.H";:

#ifndef COLA_H
#define COLA_H


#include "nodo_colaypila.h"


template <class T> class cola
{
nodo_colaypila<T>* ult, pri;
public:
cola<T>();
void anade(T&);
T saca();
void print() const;
virtual ~cola();


};




#endif // COLA_H

";可乐.CPP";:

#include "cola.h"
#include "nodo_colaypila.h"


#include <iostream>


using namespace std;


template <class T> cola<T>::cola()
{
pri = NULL;
ult = NULL;//ctor
}


template <class T> void cola<T>::anade(T& valor)
{
nodo_colaypila <T> * nuevo;


if (ult)
{
nuevo = new nodo_colaypila<T> (valor);
ult->sig = nuevo;
ult = nuevo;
}
if (!pri)
{
pri = nuevo;
}
}


template <class T> T cola<T>::saca()
{
nodo_colaypila <T> * aux;
T valor;


aux = pri;
if (!aux)
{
return 0;
}
pri = aux->sig;
valor = aux->elem;
delete aux;
if(!pri)
{
ult = NULL;
}
return valor;
}


template <class T> cola<T>::~cola()
{
while(pri)
{
saca();
}//dtor
}


template <class T> void cola<T>::print() const
{
nodo_colaypila <T> * aux;
aux = pri;
while(aux)
{
cout << aux->elem << endl;
aux = aux->sig;
}
}

然后,我有一个程序来测试这些功能如下:

";main.CPP";

#include <iostream>
#include "cola.h"
#include "nodo_colaypila.h"


using namespace std;


int main()
{
float a, b, c;
string d, e, f;
cola<float> flo;
cola<string> str;


a = 3.14;
b = 2.71;
c = 6.02;
flo.anade(a);
flo.anade(b);
flo.anade(c);
flo.print();
cout << endl;


d = "John";
e = "Mark";
f = "Matthew";
str.anade(d);
str.anade(e);
str.anade(f);
cout << endl;


c = flo.saca();
cout << "First In First Out Float: " << c << endl;
cout << endl;


f = str.saca();
cout << "First In First Out String: " << f << endl;
cout << endl;


flo.print();
cout << endl;
str.print();


cout << "Hello world!" << endl;
return 0;
}

但是当我构建时,编译器在模板类的每个实例中抛出错误:

对“ Cola(float):Cola()”的引用未定义..(它实际上是cola ' amp;lt;' float ' amp;gt;':cola(),但我不能像这样使用它)。

如此等等。总共有17个警告,包括程序中正在调用的成员函数的警告。

这是为什么?定义了这些函数和构造函数。我认为编译器可以取代“ T ”。在模板中使用“浮点”、“字符串”或者别的什么。这就是使用模板的好处。

我在这里的某个地方读到,出于某种原因,我应该把每个函数的声明放在头文件中。对不对?如果是这样,为什么?

154301 次浏览

您必须在头文件中定义函数。
不能将源文件中的模板函数定义和头文件中的声明分开。

当模板以触发其初始化的方式使用时,编译器需要看到特定的模板定义。这就是模板通常在声明它们的头文件中定义的原因。

参考资料:
C++03标准,§14.7.2.4:

__非导出函数模板的ABC__0、非导出成员函数模板或类模板的非导出成员函数或静态数据成员__显式实例化的ABC__1。

编辑:
澄清关于评论的讨论:
从技术上讲,有三种方法可以解决这个链接问题:

  • 将定义移动到.H文件
  • .cpp文件中添加显式实例化。
  • #include.cpp文件在__定义模板ABC__1文件使用模板。

它们各有利弊,

将定义移动到头文件可能会增加代码大小(现代编译器可以避免这一点),但肯定会增加编译时间。

使用显式实例化方法是回到传统的类似宏的方法。另一个缺点是必须知道程序需要哪些模板类型。对于一个简单的程序,这很容易,但对于复杂的程序,这变得很难提前确定。

虽然包含CPP文件令人困惑,但同时也存在上述两种方法的问题。

我发现第一种方法最容易遵循和实施,因此提倡使用它。

这个链接解释了你错在哪里:

[35.12]为什么我不能将Templates类的定义与其声明分开,并将其放在.CPP文件中?

把你的构造函数、析构函数、方法和诸如此类的东西的定义放在你的头文件中,这将纠正这个问题。

这提供了另一种解决方案:

如何避免模板函数的链接器错误?

但是,这需要您预测模板将如何使用,并且作为一般解决方案,这是违反直觉的。它确实解决了一个极端的情况,即你开发了一个模板,供一些内部机制使用,并且你想要监督它的使用方式。

这是C++编程中的常见问题。对此有两个有效的答案。这两个答案各有利弊,你的选择将取决于上下文。常见的答案是将所有实现放在头文件中,但在某些情况下,还有另一种方法将适用。选择权在你。

模板中的代码仅仅是编译器已知的“模式”。编译器不会编译cola<float>::cola(...)cola<string>::cola(...)的构造函数,直到它被强制这样做。我们必须确保在整个编译过程中对ABC__3的构造函数__进行一次编译,否则我们将得到“ undefined reference ”错误。(这也适用于cola<T>的其他方法。)

理解问题

该问题是由于main.cppcola.cpp将首先单独编译而引起的。在main.cpp中,编译器将cola.cpp7实例化模板类cola<float>cola<string>,因为这些特定实例化在main.cpp中使用。坏消息是,这些成员函数的实现既不在main.cpp中,也不在main.cpp中包含的任何头文件中,因此编译器不能在main.o中包含这些函数的完整版本。当编译cola.cpp时,编译器也不会编译这些实例化,因为没有cola<float>cola<string>的隐式或显式实例化。请记住,在编译cola.cpp时,编译器不知道需要哪些实例化。我们不能指望它编译为cola.cpp8类型,以确保这个问题永远不会发生!(cola.cpp3,cola.cpp4,cola.cpp5,cola.cpp6……等等..)

两个答案是:

  • cola.cpp的末尾,告诉编译器需要哪些特定的模板类,强制它编译cola<float>cola<string>
  • 将成员函数的实现放在头文件中,该头文件将在任何其他“翻译单元”(如main.cpp)使用模板类时包含每一

答案1:显式实例化模板及其成员定义

cola.cpp结束中,您应该添加显式实例化所有相关模板的行,例如

template class cola<float>;
template class cola<string>;

并在nodo_colaypila.cpp的末尾添加以下两行:

template class nodo_colaypila<float>;
template class nodo_colaypila<std :: string>;

这将确保当编译器编译cola.cpp时,它将显式编译cola<float>cola<string>类的所有代码。类似地,nodo_colaypila.cpp包含nodo_colaypila<...>类的实现。

在这种方法中,您应该确保实现的所有内容都放在一个.cpp文件中(即一个翻译单元),并且明确的即时操作放在所有函数的定义之后(即文件的末尾)。

答案2:将代码复制到相关的头文件中

常见的答案是将所有代码从实现文件cola.cppnodo_colaypila.cpp移动到cola.hnodo_colaypila.h中。从长远来看,这更加灵活,因为这意味着您可以使用额外的实例化(例如,cola<char>),而不需要做更多的工作。但这可能意味着相同的函数被编译多次,在每个翻译单元中编译一次。这不是一个大问题,因为链接器将正确地忽略重复的实现。但它可能会减慢编译速度。

总结

默认的答案是将所有的实现放在头文件中,例如STL和我们编写的大多数代码中都会使用这个答案。但在更私密的项目中,您将对实例化哪些特定模板类有更多的了解和控制。事实上,此“错误”可能会被视为一项功能,因为它可以防止代码用户意外使用您尚未测试或计划的实例化(“我知道这适用于cola<float>cola<string>,如果您想使用其他功能,请先告诉我,以便在启用它之前验证它是否有效。”)。

最后,在您的问题中,代码中还有其他三个小错误:

  • 在nodo_colaypila.H的末尾缺少#endif
  • 在COLA.H中,nodo_colaypila<T>* ult, pri;nodo_colaypila<T> *ult, *pri;-两者都是指针。
  • NODO_COLAYPILA.CPP:默认参数应该在ABC__0__头文件中,而不是在此实现文件中。