【问题标题】:Why do I get "unresolved external symbol" errors when using templates? [duplicate]为什么在使用模板时会出现“未解析的外部符号”错误? [复制]
【发布时间】:2010-10-02 04:27:24
【问题描述】:

当我使用模板为类编写 C++ 代码并在源 (CPP) 文件和头 (H) 文件之间拆分代码时,在链接最终可执行文件,尽管目标文件已正确构建并包含在链接中。这里发生了什么,我该如何解决?

【问题讨论】:

标签: c++ templates linker


【解决方案1】:

模板类和函数在使用之前不会被实例化,通常在单独的 .cpp 文件中(例如程序源)。使用模板时,编译器需要该函数的完整代码才能构建具有适当类型的正确函数。但是,在这种情况下,该函数的代码在模板的源文件中有详细说明,因此不可用。

由于所有这些,编译器只是假设它是在其他地方定义的,并且只插入对模板化函数的调用。在编译模板的源文件时,程序源中使用的特定模板类型并没有在那里使用,因此它仍然不会生成函数所需的代码。这会导致无法解析的外部符号。

对此可用的解决方案是:

  1. 包括完整的定义 中的成员函数 模板的头文件并且没有 模板的源文件,
  2. 定义所有的成员函数 模板的源文件为 "inline" (更新:[这不适用于 Visual Studio 2017+]),或
  3. 定义成员 模板源中的函数 使用“导出”关键字。 不幸的是,这不受支持 由许多编译器编写。(更新:this has been removed from the standard as of C++11。)

当编译器试图在程序源代码中构建类型化函数时,通过让编译器访问模板化函数的完整代码,1 和 2 基本上都解决了这个问题。

【讨论】:

  • 在 (3) 中,您输入错误。您可能指的是关键字而不是键盘。我看不出将函数定义为“内联”会有什么帮助。您需要将它们放在标题中或使用您需要的类型显式实例化模板。
  • 您可能应该改写 (2)。不知道你是什么意思
  • “export”关键字也提供了完整的定义。它可能是某种稍微编码的形式,例如编译器解析树,但它隐藏得不是很好。当然我想机器码也不能很好地隐藏源代码。
  • 我有同样的问题,这没有回答...我不知道为什么这已被接受为答案。包含成员函数的完整定义是可行的,但在我看来,它代表我们的程序缺乏安全性,并且通常是一种糟糕的做法,会导致代码杂乱无章。
  • @JohannesSchaub-litb 如果每件事都需要在头文件中(定义)那么.cpp文件有什么用?
【解决方案2】:

另一种选择是将代码放在 cpp 文件中,并在同一个 cpp 文件中添加具有您希望使用的类型的模板的显式实例化。如果您知道您只会将它用于您预先知道的几种类型,这将非常有用。

【讨论】:

  • 所以本质上说 f**** ***u 是模块化、重用、单一职责和关注点分离......以及通用编程的重点拥有可以与您想要的任何类型一起使用的通用类模板类事先不知道它将用于什么?
  • @jbx 我是说对于basic_string<T> 之类的东西,你只会将它与charwchar_t 一起使用,所以如果将所有实现放在标题中是担心,在 cpp 中实例化它是一种选择。代码是你的命令,反之亦然。
  • 在我看来完全击败了模板。您的示例只是一个例外(如果仅针对 2 种类型,可以说应该使用重载编写,但这是另一个参数)。模板编程应该是关于创建独立于与之交互的类型而工作的东西,而无需事先知道。预测类型将是什么与它的整个目的背道而驰。它只是“解决”痛苦的一种不好的做法,但仅仅因为你可以并不意味着你应该这样做。
  • @jbx 错误。模板编程就是不重复自己。如果你写的东西恰好是超级通用和精彩的,对你有好处,但这绝不是必要的。如果它允许我只编写一个类或函数而不是 2 个,它就实现了它的目的。即使是标准容器也不独立于类型,它们依赖于诸如默认 c'tor 和 move c'tor 之类的东西。
  • 你说这不是不重复你自己,那么在下一句你说如果它允许你只写一个类或函数而不是2,它就达到了它的目的,那么如何那不是关于不重复自己吗?如果每次需要将模板的头文件用于另一种新类型时,您的代码如何遵循开放封闭原则。让我们承认,由于 C++ 的限制,这是一个 hack,这是与许多其他设计缺陷一样令人讨厌的设计缺陷之一。
【解决方案3】:

对于包含 .h 文件的每个文件,您应该插入两行:

#include "MyfileWithTemplatesDeclaration.h"
#include "MyfileWithTemplatesDefinition.cpp"

样本

#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 常量中

#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 文件,并且根据您的项目设置,这甚至可能会给编译器带来单独的麻烦
猜你喜欢
  • 2012-07-15
  • 2010-12-23
  • 2011-08-12
  • 2012-03-11
  • 1970-01-01
  • 2019-08-28
  • 2012-05-30
  • 2015-10-11
相关资源
最近更新 更多