Why do I get “unresolved external symbol” errors when using templates?
当我使用模板为类编写C++代码并在源(CPP)文件和报头(H)文件之间拆分代码时,在连接最终执行时,会得到大量的"未解决的外部符号"错误,尽管对象文件被正确地构建并包含在链接中。这里发生了什么事,我怎么修理?
- 另请参见stackoverflow.com/questions/495021/…
模板化的类和函数在使用之前不会被实例化,通常在单独的.cpp文件(例如程序源)中。使用模板时,编译器需要该函数的完整代码,才能用适当的类型构建正确的函数。但是,在这种情况下,该函数的代码在模板的源文件中详细说明,因此不可用。
因此,编译器只假设它是在别处定义的,并且只插入对模板化函数的调用。在编译模板的源文件时,程序源中使用的特定模板类型没有在那里使用,因此它仍然不会生成函数所需的代码。这将导致无法解析的外部符号。
可用于此目的的解决方案是:
包括对中的成员函数模板的头文件模板的源文件,
在中定义所有成员函数模板的源文件为"内联",或
定义成员模板源中的函数使用"export"关键字。很遗憾,这不受支持通过很多编译器。(Update:这已经从C++ 11中的标准中删除)。
当编译器试图在程序源代码中构建类型化函数时,1和2都通过赋予它对模板化函数的完整代码的访问权,基本上解决了这个问题。
- 在(3)中,您有一个输入错误。你可能指的是关键字而不是键盘。我不知道将函数定义为"inline"有什么帮助。您需要将它们放在头中,或者使用需要的类型显式地实例化模板。
- 你应该重新措辞(2)。不知道你说的是什么意思
- "export"关键字也提供了完整的定义。它可能是一些稍微编码的形式,比如编译器解析树,但它并没有很好地隐藏起来。当然,我认为机器代码也不能很好地隐藏源代码。
- 我也有同样的问题,但这不能解决…我不知道这为什么被接受为答案。包括对成员函数的完整定义是可行的,但在我看来,它代表着我们的程序缺乏安全性,而且通常是一种不好的做法,这将导致代码无组织。
- 我也被(2)迷惑了。从2009年开始就有了(哇,那时我不知道什么是模板)。而且,将函数放入.h文件中也没有帮助。
- @johannesschoub litb如果所有的东西都需要在头文件(definition)中,那么.cpp文件的用途是什么?
另一个选项是将代码放在cpp文件中,并在同一cpp文件中添加模板的显式实例化,该模板具有您希望使用的类型。如果你知道你只会将它用于一些你事先知道的类型,那么这是很有用的。
- 所以本质上说F*****U是模块化、重用、单一责任、关注点分离……通用编程的关键是要有通用类,这些类可以与任何类型一起使用,而不需要模板类事先知道它将用于什么?
- @jbx我要说的是,对于像basic_string这样的东西,您只能将它与char或wchar_t一起使用,所以如果将所有实现放在头中是一个问题,那么在cpp中实例化它是一个选项。代码由您来命令,反之亦然。
- 在我看来,破坏了模板的全部观点。您的示例只是一个异常(如果它只针对两种类型,那么可以说应该使用重载编写,但这是另一个参数)。模板编程应该是在不事先知道的情况下,创建独立于与之交互的类型工作的东西。预测这些类型将是什么,这违背了它的全部目的。这只是一个"解决"痛苦的坏习惯,但仅仅因为你不能,并不意味着你应该这样做。
- JBX错了。模板编程是关于不要重复自己。如果你写的东西恰巧是超级通用的和美妙的,对你来说是好的,但这绝对不是必要的。如果它允许我只写一个类或函数而不是2,那么它就实现了它的目的。即使是标准容器也不独立于类型,它们依赖于诸如默认的c'tor和移动c'tor之类的东西。
- 你说这不是不重复你自己,然后在下一句话里你说如果它只允许你写一个类或函数是2的头,它就实现了它的目的,那么不重复你自己又是怎么回事呢?如果每次需要将模板的头文件用于另一个新类型时都需要修改,那么您的代码如何遵循打开-关闭原则呢?让我们承认,这是一个黑客,由于限制的C++,其中一个恼人的设计缺陷,像许多其他。
对于包含.h文件的每个文件,应插入两行:
1 2
| #include"MyfileWithTemplatesDeclaration.h"
#include"MyfileWithTemplatesDefinition.cpp" |
样品
1 2 3 4 5 6 7 8 9 10 11 12
| #include"list.h"
#include"list.cpp" //<---for to fix bug link err 2019
int main(int argc, _TCHAR* argv[])
{
list<int> my_list;
my_list.add_end(3);
.
.
} |
另外,不要忘记将声明类放在Centinel常量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #ifndef LIST_H
#define LIST_H
#include <iostream>
.
.
template <class T>
class list
{
private:
int m_size,
m_count_nodes;
T m_line;
node<T> *m_head;
public:
list(void);
~list(void);
void add_end(T);
void print();
};
#endif |
- 我不认为这是个好主意。包括.cpp文件发送错误消息。如果希望用户同时包含这两个文件。将它们命名为code.h和code_impl.h或类似的名称。
- 我同意。几乎没有理由在源文件中包含一个.cpp文件,根据项目设置,这甚至可能会给编译器带来单独的麻烦。