这个问题在这里已经有了答案:为什么模板只能在头文件中实现? (17 个回答) 7 年前关闭。
我不知道为什么会发生这种情况,因为我认为我已经正确声明和定义了所有内容。
我有以下程序,用模板设计。这是一个队列的简单实现,具有成员函数“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
}
之后,队列模板类及其功能的定义和声明:
“可乐.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;
}
}
然后,我有一个程序来测试这些功能,如下所示:
“主.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'<'float'>'::cola(),但这不允许我那样使用它。)
等等。总共有 17 个警告,计算程序中调用的成员函数的警告。
为什么是这样?那些函数和构造函数是被定义的。我认为编译器可以将模板中的“T”替换为“float”、“string”等;这就是使用模板的优势。
我在这里读到,出于某种原因,我应该将每个函数的声明放在头文件中。那正确吗?如果是这样,为什么?
#endif
nodo_colaypila<T>* ult, pri;
应该是 nodo_colaypila<T> *ult, *pri;
。两者都应该是指针,对吧?
这是 C++ 编程中的一个常见问题。对此有两个有效的答案。这两种答案都有优点和缺点,您的选择将取决于上下文。常见的答案是将所有实现放在头文件中,但在某些情况下还有另一种方法将是合适的。这是你的选择。
模板中的代码只是编译器已知的“模式”。编译器在被迫编译之前不会编译构造函数 cola<float>::cola(...)
和 cola<string>::cola(...)
。而且我们必须确保构造函数在整个编译过程中至少发生一次这种编译,否则我们将得到“未定义引用”错误。 (这也适用于 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
将成员函数的实现放在一个头文件中,每次任何其他“翻译单元”(例如 main.cpp)使用模板类时都会包含该头文件。
答案 1:显式实例化模板及其成员定义
在 cola.cpp
的 end 处,您应该添加行显式实例化所有相关模板,例如
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 使用的默认答案以及我们任何人都会编写的大多数代码中的默认答案是将所有实现放在头文件中。但是在一个更私人的项目中,您将有更多的知识和控制哪些特定的模板类将被实例化。事实上,这个“错误”可能被视为一项功能,因为它可以防止您的代码用户意外使用您尚未测试或计划的实例(“我知道这适用于 cola<float>
和 cola<string>
,如果您想要使用其他东西,请先告诉我,并且会在启用它之前验证它是否有效。”)。
最后,您的问题中的代码中还有其他三个小错别字:
您在 nodo_colaypila.h 末尾缺少 #endif
在 cola.h nodo_colaypila
nodo_colaypila.cpp:默认参数应该在头文件nodo_colaypila.h中,不在这个实现文件中。
您必须在头文件中定义函数。您不能将模板函数的定义分离到源文件中,而将声明分离到头文件中。
当以触发其实例化的方式使用模板时,编译器需要查看该特定模板定义。这就是模板通常在声明它们的头文件中定义的原因。
参考:C++03 标准,第 14.7.2.4 节:
类模板的非导出函数模板、非导出成员函数模板或非导出成员函数或静态数据成员的定义应出现在显式实例化它的每个翻译单元中。
编辑:澄清对评论的讨论:从技术上讲,有三种方法可以解决这个链接问题:
将定义移动到 .h 文件
在 .cpp 文件中添加显式实例化。
#include 定义模板的.cpp 文件在.cpp 文件中使用模板。
他们每个人都有自己的优点和缺点,
将定义移动到头文件可能会增加代码大小(现代编译器可以避免这种情况),但肯定会增加编译时间。
使用显式实例化方法正在回到传统的类似宏的方法。另一个缺点是必须知道程序需要哪些模板类型。对于简单的程序,这很容易,但对于复杂的程序,这变得难以预先确定。
虽然包含 cpp 文件令人困惑,但同时也存在上述两种方法的问题。
我发现第一种方法最容易遵循和实施,因此提倡使用它。
这个链接解释了你哪里出错了:
将构造函数、析构函数方法和诸如此类的定义放在头文件中,这将解决问题。
这提供了另一种解决方案:
How can I avoid linker errors with my template functions?
但是,这需要您预测如何使用您的模板,并且作为一般解决方案,这是违反直觉的。它确实解决了极端情况,尽管您开发了一个供某些内部机制使用的模板,并且您希望监管它的使用方式。
不定期副业成功案例分享
void*
。仅仅因为几个函数就将所有的类实现移到一个头文件中是非常糟糕的。