“未定义的引用”模板类的构造函数
我不知道这是为什么发生,因为我认为我已经正确地声明和定义了一切。
我有以下程序,用模板devise。 这是一个简单的队列实现,成员函数为“add”,“substract”和“print”。
我已经在罚款“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 }
之后,队列模板类的定义和声明及其function如下:
“cola.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
“cola.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; } }
然后,我有一个程序来testing这些function如下:
“的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; }
但是当我编译时,编译器会在模板类的每个实例中抛出错误:
未定义的引用'可乐(浮法)::可乐()'… (它实际上可乐'<'浮法'>'::可乐(),但这不让我使用它。
等等。 总共有17个警告,算出程序中调用成员函数的警告。
为什么是这样? 这些函数和构造函数在WERE中定义。 我认为编译器可以用“float”,“string”或其他什么来replace模板中的“T” 这是使用模板的优势。
我在这里读了一些地方,我应该把每个函数的声明放在头文件中。 是对的吗? 如果是这样,为什么?
提前致谢。
这是C ++编程中的一个常见问题。 有两个有效的答案。 这两个答案都有优点和缺点,你的select将取决于上下文。 常见的答案是把所有的实现放在头文件中,但是在另外一些情况下也是适用的。 这是你的select。
模板中的代码仅仅是编译器已知的“模式”。 编译器将不会编译构造函数cola<float>::cola(...)
和cola<string>::cola(...)
直到强制执行为止。 而且我们必须确保这个编译在整个编译过程中至less一次发生在构造函数中,否则我们会得到'undefined reference'错误。 (这也适用于cola<T>
的其他方法)。
了解问题
这个问题是由main.cpp
和cola.cpp
先分别编译的事实引起的。 在main.cpp
,编译器将隐式地实例化模板类cola<float>
和cola<string>
因为这些特定的实例在main.cpp
中使用。 坏消息是这些成员函数的实现不在main.cpp
,也不在main.cpp
中包含的头文件中,因此编译器不能在main.o
包含这些函数的完整版本。 当编译cola.cpp
,编译器也不会编译这些实例,因为没有cola<float>
或cola<float>
实例。 请记住,编译cola.cpp
,编译器不知道需要哪个实例。 而且我们也不能期望它能够针对每种types进行编译,以确保这个问题永远不会发生! ( cola<int>
, cola<char>
, cola<ostream>
, cola< cola<int> >
…等等)
这两个答案是:
- 告诉编译器,在
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.cpp
和nodo_colaypila.cpp
所有代码移动到cola.h
和nodo_colaypila.h
。 从长远来看,这是更灵活的,因为它意味着你可以使用额外的实例(如cola<char>
),而无需任何工作。 但是这可能意味着相同的函数被多次编译,每个编译单元都会编译一次。 这不是一个大问题,因为链接器将正确地忽略重复的实现。 但是这可能会减慢汇编的速度。
概要
STL使用的默认答案,例如我们任何人写的大部分代码都是把所有的实现放在头文件中。 但在一个更私人的项目中,你将有更多的知识和控制哪些特定的模板类将被实例化。 事实上,这个“bug”可能被视为一个function,因为它会阻止你的代码的用户意外地使用你没有testing或计划的实例化(“我知道这适用于cola<float>
和cola<string>
如果你想使用其他的东西,首先告诉我,并将启用它之前,可以validation它的作品。“)。
最后,在你的问题的代码中还有三个小错别字:
- 您在nodo_colaypila.h结尾缺less
#endif
- 在cola.h中
nodo_colaypila<T>* ult, pri;
应该是nodo_colaypila<T> *ult, *pri;
– 都是指针。 - nodo_colaypila.cpp:默认参数应该在头文件
nodo_colaypila.h
,而不是在这个实现文件中。
你将不得不在头文件中定义函数。
您不能将模板函数的定义分隔到源文件中,并在头文件中声明。
当模板以触发其整型的方式使用时,编译器需要看到特定的模板定义。 这是模板通常在声明它们的头文件中定义的原因。
参考:
C ++ 03标准,§14.7.2.4:
非导出函数模板,非导出成员函数模板或类模板的非导出成员函数或静态数据成员的定义应存在于明确实例化的每个翻译单元中。
编辑:
澄清对评论的讨论:
从技术上讲,解决这个链接问题有三种方法:
- 将定义移动到.h文件
- 在
.cpp
文件中添加显式的实例。 -
#include
使用模板在.cpp
文件中定义模板的.cpp
文件。
他们每个人都有自己的优点和缺点,
将定义移动到头文件可能会增加代码的大小(现代编译器可以避免这种情况),但会增加编译时间。
使用显式实例化方法正在回到传统的macros观方法。另一个缺点是有必要知道程序需要哪些模板types。 对于一个简单的程序来说,这很容易,但对于复杂的程序,这很难提前确定。
虽然包括cpp文件混淆同时共享上述两种方法的问题。
我发现第一种方法最容易遵循和实施,因此提倡使用它。
这个链接可以解释你错误的地方:
[35.12]为什么我不能将我的模板类的定义从它的声明中分离出来并放在一个.cpp文件中?
把你的构造函数的定义,析构函数的方法和你的头文件中的东西放在一起,这样可以解决这个问题。
这提供了另一个解
如何避免使用我的模板函数的链接器错误?
然而这要求你预测你的模板将如何被使用,并且作为一个通用的解决scheme是违反直觉的。 它确实解决了angular落的情况,虽然你在哪里开发了一个内部机制使用的模板,并且要监视它的使用方式。