“未定义的引用”模板类构造函数
我不知道为什么会发生这种情况,因为我认为我已经正确声明和定义了所有内容。
我有以下程序,是用模板设计的。这是队列的简单实现,具有成员函数“add”、“subtract”和“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” 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'<'float'>'::cola(),但这不允许我那样使用它。)
等等。算上程序中调用的成员函数,总共有 17 个警告。
这是为什么呢?这些函数和构造函数已定义。我认为编译器可以将模板中的“T”替换为“float”、“string”或其他什么;这就是使用模板的优点。
我在这里读到,出于某种原因,我应该将每个函数的声明放在头文件中。是这样吗?如果是这样,为什么?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是 C++ 编程中的一个常见问题。对此有两个有效的答案。这两个答案都有优点和缺点,您的选择将取决于上下文。常见的答案是将所有实现放在头文件中,但在某些情况下还有另一种方法适用。选择权在你。
模板中的代码只是编译器已知的“模式”。编译器不会编译构造函数
cola::cola(...)
和cola::cola(...)
,直到它被迫这样做。而且我们必须确保在整个编译过程中构造函数至少发生一次这种编译,否则我们将得到“未定义引用”错误。 (这也适用于cola
的其他方法。)理解问题
该问题是由
main.cpp
和cola.cpp 引起的
会先单独编译。在main.cpp
中,编译器将隐式实例化模板类cola
和cola
因为这些特定的实例化是在main.cpp
中使用的。坏消息是,这些成员函数的实现不在main.cpp
中,也不在main.cpp
中包含的任何头文件中,因此编译器无法在main.o
中包含这些函数的完整版本。编译 cola.cpp 时,编译器也不会编译这些实例化,因为没有cola
或cola 的隐式或显式实例化;
。请记住,在编译 cola.cpp 时,编译器不知道需要哪些实例化;我们不能指望它能够针对每种类型进行编译,以确保此问题永远不会发生! (cola
、cola
、cola
、cola> ...等等...)
两个答案是:
cola.cpp
末尾告诉编译器,需要哪些特定的模板类,强制其编译cola
和cola
。main.cpp
)使用模板类时,都会包含该头文件。答案 1:显式实例化模板及其成员定义
在
cola.cpp
的末尾,您应该添加显式实例化所有相关模板的行,例如
nodo_colaypila.cpp
末尾添加以下两行:这将确保编译器在编译
cola.cpp
时显式编译cola
和cola
类。类似地,nodo_colaypila.cpp
包含nodo_colaypila<...>
类的实现。在这种方法中,您应该确保所有实现都放置在一个
.cpp
文件中(即一个翻译单元),并且显式实例化放置在所有函数的定义之后(即位于文件末尾)。答案2:将代码复制到相关头文件中
常见的答案是将实现文件
cola.cpp
和nodo_colaypila.cpp
中的所有代码移动到cola .h
和nodo_colaypila.h
。从长远来看,这更加灵活,因为这意味着您可以使用额外的实例化(例如cola
),而无需进行任何其他工作。但这可能意味着相同的函数会被编译多次,每个翻译单元一次。这不是一个大问题,因为链接器将正确地忽略重复的实现。但它可能会稍微减慢编译速度。总结
例如,STL 使用的默认答案以及我们任何人将编写的大多数代码中使用的默认答案是将所有实现放在头文件中。但在更私密的项目中,您将对将实例化哪些特定模板类有更多的了解和控制。事实上,这个“错误”可能被视为一项功能,因为它可以阻止代码的用户意外使用您尚未测试或计划的实例化(“我知道这适用于
cola
> 和cola
,如果您想使用其他东西,请先告诉我,我们可以在启用它之前验证它是否有效。”)。最后,您问题中的代码中还有其他三个小拼写错误:
#endif
nodo_colaypila* ult, pri 中的 ;
应该是nodo_colaypila; *ult, *pri;
- 两者都是指针。nodo_colaypila.h
中,而不是在此实现文件中。This is a common question in C++ programming. There are two valid answers to this. There are advantages and disadvantages to both answers and your choice will depend on context. The common answer is to put all the implementation in the header file, but there's another approach will will be suitable in some cases. The choice is yours.
The code in a template is merely a 'pattern' known to the compiler. The compiler won't compile the constructors
cola<float>::cola(...)
andcola<string>::cola(...)
until it is forced to do so. And we must ensure that this compilation happens for the constructors at least once in the entire compilation process, or we will get the 'undefined reference' error. (This applies to the other methods ofcola<T>
also.)Understanding the problem
The problem is caused by the fact that
main.cpp
andcola.cpp
will be compiled separately first. Inmain.cpp
, the compiler will implicitly instantiate the template classescola<float>
andcola<string>
because those particular instantiations are used inmain.cpp
. The bad news is that the implementations of those member functions are not inmain.cpp
, nor in any header file included inmain.cpp
, and therefore the compiler can't include complete versions of those functions inmain.o
. When compilingcola.cpp
, the compiler won't compile those instantiations either, because there are no implicit or explicit instantiations ofcola<float>
orcola<string>
. Remember, when compilingcola.cpp
, the compiler has no clue which instantiations will be needed; and we can't expect it to compile for every type in order to ensure this problem never happens! (cola<int>
,cola<char>
,cola<ostream>
,cola< cola<int> >
... and so on ...)The two answers are:
cola.cpp
, which particular template classes will be required, forcing it to compilecola<float>
andcola<string>
.main.cpp
) uses the template class.Answer 1: Explicitly instantiate the template, and its member definitions
At the end of
cola.cpp
, you should add lines explicitly instantiating all the relevant templates, such asand you add the following two lines at the end of
nodo_colaypila.cpp
:This will ensure that, when the compiler is compiling
cola.cpp
that it will explicitly compile all the code for thecola<float>
andcola<string>
classes. Similarly,nodo_colaypila.cpp
contains the implementations of thenodo_colaypila<...>
classes.In this approach, you should ensure that all the of the implementation is placed into one
.cpp
file (i.e. one translation unit) and that the explicit instantation is placed after the definition of all the functions (i.e. at the end of the file).Answer 2: Copy the code into the relevant header file
The common answer is to move all the code from the implementation files
cola.cpp
andnodo_colaypila.cpp
intocola.h
andnodo_colaypila.h
. In the long run, this is more flexible as it means you can use extra instantiations (e.g.cola<char>
) without any more work. But it could mean the same functions are compiled many times, once in each translation unit. This is not a big problem, as the linker will correctly ignore the duplicate implementations. But it might slow down the compilation a little.Summary
The default answer, used by the STL for example and in most of the code that any of us will write, is to put all the implementations in the header files. But in a more private project, you will have more knowledge and control of which particular template classes will be instantiated. In fact, this 'bug' might be seen as a feature, as it stops users of your code from accidentally using instantiations you have not tested for or planned for ("I know this works for
cola<float>
andcola<string>
, if you want to use something else, tell me first and will can verify it works before enabling it.").Finally, there are three other minor typos in the code in your question:
#endif
at the end of nodo_colaypila.hnodo_colaypila<T>* ult, pri;
should benodo_colaypila<T> *ult, *pri;
- both are pointers.nodo_colaypila.h
, not in this implementation file.您必须在头文件中定义函数。
您不能将模板函数的定义分离到源文件中并将声明分离到头文件中。
当以触发其实例的方式使用模板时,编译器需要查看特定的模板定义。这就是模板通常在声明它们的头文件中定义的原因。
参考:
C++03 标准,第 14.7.2.4 条:
编辑:
为了澄清评论的讨论:
从技术上讲,可以通过三种方法来解决此链接问题:
.cpp
文件中添加显式实例化。#include
使用模板在.cpp
文件中定义模板的.cpp
文件。它们各有优缺点,
将定义移至头文件可能会增加代码大小(现代编译器可以避免这种情况),但肯定会增加编译时间。
使用显式实例化方法正在回到传统的类似宏的方法。另一个缺点是必须知道程序需要哪些模板类型。对于简单的程序,这很容易,但对于复杂的程序,这很难提前确定。
虽然包含 cpp 文件令人困惑,但同时也存在上述两种方法的问题。
我发现第一种方法最容易遵循和实施,因此提倡使用它。
You will have to define the functions inside your header file.
You cannot separate definition of template functions in to the source file and declarations in to header file.
When a template is used in a way that triggers its intstantation, a compiler needs to see that particular templates definition. This is the reason templates are often defined in the header file in which they are declared.
Reference:
C++03 standard, § 14.7.2.4:
EDIT:
To clarify the discussion on the comments:
Technically, there are three ways to get around this linking problem:
.cpp
file.#include
the.cpp
file defining the template at the.cpp
file using the template.Each of them have their pros and cons,
Moving the defintions to header files may increase the code size(modern day compilers can avoid this) but will increase the compilation time for sure.
Using the explicit instantiation approach is moving back on to traditional macro like approach.Another disadvantage is that it is necessary to know which template types are needed by the program. For a simple program this is easy but for complicated program this becomes difficult to determine in advance.
While including cpp files is confusing at the same time shares the problems of both above approaches.
I find first method the easiest to follow and implement and hence advocte using it.
此链接解释了您出错的地方:
[35.12] 为什么不能我将模板类的定义与其声明分开并将其放入 .cpp 文件中?
将构造函数、析构函数方法等的定义放在头文件中,这将纠正问题。
这提供了另一种解决方案:
如何我可以避免模板函数出现链接器错误吗?
但是,这需要您预测模板的使用方式,并且作为一般解决方案,这是违反直觉的。它确实解决了极端情况,尽管您开发了一个由某些内部机制使用的模板,并且您想要监管它的使用方式。
This link explains where you're going wrong:
[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.
This offers another solution:
How can I avoid linker errors with my template functions?
However this requires you to anticipate how your template will be used and, as a general solution, is counter-intuitive. It does solve the corner case though where you develop a template to be used by some internal mechanism, and you want to police the manner in which it is used.