这个问题在这里已有答案:
我不知道为什么会发生这种情况,因为我认为我已经正确地声明和定义了所有内容 .
我有以下程序,使用模板设计 . 这是一个简单的队列实现,成员函数为“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
}
然后,队列模板类的定义和声明及其功能:
“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;
}
}
然后,我有一个程序来测试这些函数如下:
“的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;
}
但是当我构建时,编译器会在模板类的每个实例中抛出错误:
undefined reference to `cola(float)::cola()'... ('s actually cola' <'float'> '::cola(), but this doesn' t让我这样使用它 . )
等等 . 总共有17个警告,计算程序中调用的成员函数的警告 .
为什么是这样?那些函数和构造函数已定义 . 我认为编译器可以用“float”,“string”等替换模板中的“T”;这是使用模板的优势 .
我在这里读到,我应该将头文件中的每个函数的声明放在某个原因 . 是对的吗?如果是这样,为什么?
提前致谢 .
3 回答
这是C编程中的常见问题 . 这有两个有效的答案 . 两种答案都有优点和缺点,您的选择取决于背景 . 常见的答案是将所有实现放在头文件中,但在某些情况下,另一种方法将是合适的 . 这是你的选择 .
模板中的代码只是编译器已知的'pattern' . 在强制执行此操作之前,编译器不会编译构造函数
cola<float>::cola(...)
和cola<string>::cola(...)
. 并且我们必须确保在整个编译过程中至少对构造函数进行一次编译,否则我们将得到'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<string>
的隐式或显式实例化 . 请记住,在编译cola.cpp
时,编译器不知道需要哪些实例化;我们不能指望它为每种类型编译,以确保永远不会发生这个问题! (cola<int>
,cola<char>
,cola<ostream>
,cola< cola<int> >
......等等......)这两个答案是:
告诉编译器,在
cola.cpp
的末尾,需要哪些特定的模板类,强制它编译cola<float>
和cola<string>
.将成员函数的实现放在头文件中,每当其他任何'translation unit'(例如
main.cpp
)使用模板类时,都将包含该头文件 .答案1:明确地实例化模板及其成员定义
在
cola.cpp
的末尾,您应该添加明确实例化所有相关模板的行,例如并在
nodo_colaypila.cpp
末尾添加以下两行:这将确保在编译器编译
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'可能被视为一个功能,因为它会阻止您的代码用户意外地使用您未测试或计划的实例化(“我知道这适用于
cola<float>
和cola<string>
,如果您想使用其他内容,先告诉我,在启用之前可以验证它是否有效 . “) .最后,您的问题代码中还有另外三个小错别字:
您在nodo_colaypila.h末尾缺少
#endif
cola.h
nodo_colaypila<T>* ult, pri;
中的应该是
nodo_colaypila<T> *ult, *pri;
- 两者都是指针 .nodo_colaypila.cpp:默认参数应该在头文件
nodo_colaypila.h
中,而不是在此实现文件中 .您必须在头文件中定义函数 .
您不能将模板函数的定义分离到源文件和声明到头文件中 .
当模板以触发其定位的方式使用时,编译器需要查看该特定模板定义 . 这就是模板通常在声明它们的头文件中定义的原因 .
参考:
C++03 standard, § 14.7.2.4:
EDIT:
澄清对评论的讨论:
从技术上讲,有三种方法可以解决这个链接问题:
将定义移动到.h文件
在
.cpp
文件中添加显式实例化 .#include
.cpp
文件使用模板在.cpp
文件中定义模板 .他们每个人都有自己的优点和缺点,
将定义移动到头文件可能会增加代码大小(现代编译器可以避免这种情况),但肯定会增加编译时间 .
使用显式实例化方法正在转向传统的宏方法 . 另一个缺点是必须知道程序需要哪些模板类型 . 对于简单的程序,这很容易,但对于复杂的程序,这变得难以提前确定 .
虽然包含cpp文件同时令人困惑,但同时存在上述两种方法的问题 .
我发现第一种方法最容易遵循和实施,因此主张使用它 .
此链接说明了您出错的地方:
[35.12] Why can't I separate the definition of my templates class from its declaration and put it inside a .cpp file?
Place the definition of your constructors, destructors methods and whatnot in your header file, and that will correct the problem.
这提供了另一种解决方
How can I avoid linker errors with my template functions?
但是,这需要您预测模板的使用方式,并且作为一般解决方案,这是违反直觉的 . 它确实解决了角落的情况,虽然你开发了一个模板供一些内部机制使用,你想要警告它的使用方式 .