【问题标题】:Why does this virtual destructor trigger an unresolved external?为什么这个虚拟析构函数会触发一个未解析的外部?
【发布时间】:2011-04-03 09:53:36
【问题描述】:

考虑以下几点:

在X.h:

class X
{
    X();
    virtual ~X();
};

X.cpp:

#include "X.h"

X::X()
{}

尝试构建它(我正在使用 .dll 目标来避免丢失的 main 错误,并且我正在使用 Visual Studio 2010):

错误 1 ​​错误 LNK2001: 无法解析的外部符号“private: virtual __thiscall X::~X(void)” (??1X@@EAE@XZ)

不过,小的修改会导致构建成功:

X.h:

class X
{
    inline X(); // Now inlined, and everything builds
    virtual ~X();
};

或

X.h:

class X
{
    X();
    ~X(); // No longer virtual, and everything builds
};

当 .dtor 是虚拟的或 .ctor 未内联时,是什么导致链接器中未解析的外部?

编辑:

或者,也许更有趣的是,如果我将析构函数设为非虚拟,或者如果我内联构造函数,为什么我不得到一个未解析的外部?

【问题讨论】:

  • 我确定我理解问题/变体,但底线是:如果要调用它,它需要一个定义。在您的第一个变体中,您从未定义析构函数,这正是错误所说的。我看不到变体是如何构建的,因为我仍然看不到构造函数或析构函数的定义。
  • 但他们确实如此。因此我的困惑。这是一个非常简单的代码示例,我的同事都没有对此行为给出明确的答案。最好的猜测是某些东西是未定义的,我们最终会进入特定于供应商的领域,但我很好奇究竟是什么未定义会触发不同的行为。
  • 除非您向我们展示您正在构建的所有内容,否则无法告诉我们!
  • @Martin York:这就是我正在构建的一切!字面上地!好的,我可能在其中一个文件前省略了回车,但说真的,这就是项目中的所有内容。您可以在 20 秒内将其复制到您自己的暂存库中。就是这么简单。
  • 在第一个中它需要析构函数的地址来构建虚拟表。在第二个中:由于从未实际构建构造函数,因此它永远不需要构建虚拟表,因此不需要析构函数的地址。在第三个中,我们在构建构造函数时不需要析构函数的地址(因为它不再在虚拟表中);。

标签: c++ destructor unresolved-external virtual-functions


【解决方案1】:

情况一:

你有构造函数的代码。
因此它将构造函数构建到目标文件中。构造函数需要将析构函数的地址放入虚拟表中,因为找不到它构造函数无法构建。

情况2:(内联构造函数)

编译器决定它不需要构建构造函数(因为它将被内联)。
因此它不会植入任何代码,因此不需要析构函数的地址。

如果你实例化一个 X 类型的对象,它会再次报错。

情况3:(非虚析构函数)

你不需要析构函数的地址来构建构造函数。
所以它不会抱怨。

如果你实例化一个 X 类型的对象,它会报错。

【讨论】:

    【解决方案2】:

    你需要给虚拟析构函数一个主体:

    
    class X
    {
        X();
        virtual ~X() {}
    };
    

    【讨论】:

    • 感谢您的建议,但这不是一个 plz-send-teh-codez how-do-i-get-rid-of-the-error 问题。相反,这是一个关于更密切地理解我们赖以谋生的工具的问题。
    【解决方案3】:

    在 C++ 中,函数必须定义当且仅当它们在您的程序中使用(参见 3.2/2 中的 ODR)。通常,如果从潜在评估的表达式中调用非虚函数,则使用。任何非纯虚函数都被视为无条件使用。当[非虚拟]特殊成员函数被使用时,定义在语言标准的专用位置。以此类推。

    • 在您的第一个示例中,您将析构函数声明为非纯虚函数。这立即意味着您的析构函数在您的程序中使用。反过来,这意味着需要该析构函数的定义。你没有提供定义,所以编译器报错。

    • 在您的 third 示例中,析构函数是非虚拟的。由于您没有在程序中使用析构函数,因此不需要定义并且代码可以编译(参见 12.4 了解析构函数的使用的详细描述)。

    • 在您的 second 示例中,您正在处理实现的一个怪癖,这是由内联构造函数这一事实触发的。由于析构函数是非纯虚函数,因此需要定义。但是,您的编译器未能检测到错误,这就是代码似乎编译成功的原因。您可以在实现的细节中挖掘此行为的原因,但从 C++ 的角度来看,此示例与第一个示例一样,原因完全相同。

    【讨论】:

      【解决方案4】:

      第一个问题的答案,

      是什么导致未解决的外部 .dtor 为虚拟时的链接器 或者当 .ctor 没有内联?

      ...很简单,你没有析构函数的定义。

      现在你的第二个问题更有趣了:

      为什么我没有得到一个未解决的 如果我制作析构函数,则为外部 非虚拟的,或者如果我内联 构造函数?

      原因是你的编译器不需要X 的析构函数,因为你从未实例化X,所以它把你的整个类都扔掉了。如果你尝试编译这个程序,你会得到一个未解析的外部:

      class X
      {
      public:
          X();
           ~X();
      };
      
      X::X() {};
      
      int main()
      {
          X x;
          return 0;
      }
      

      但是如果你注释掉X x;,它会编译得很好,正如你所观察到的。

      现在让我们回到为什么如果析构函数 if virtual 则它不会编译。我在这里推测,但我相信原因是因为你有一个虚拟析构函数,X 现在是一个多态类。为了在内存中布局多态类,使用vtable 实现多态的编译器需要每个虚函数的地址。你还没有实现X::~X,所以一个未解决的外部结果。

      为什么编译器不像 X 不是多态类时那样直接丢弃 X?这里有更多的猜测。但我预计原因是因为即使您没有直接实例化X,也不能确定您的代码中没有任何地方可以使用X,伪装成其他东西。例如,考虑一个抽象基类。在这种情况下,您永远不会直接实例化Base,Derived 的代码可能在一个完全独立的翻译单元中。所以当编译器拿到这个多态类时,即使它不知道你实例化了它,它也不能丢弃它。

      【讨论】:

        【解决方案5】:

        这些还不是一个完整的程序(甚至是一个完整的 DLL)。当您收到错误时,实际上是在帮助您,因为 X 在没有 ~X() 的定义的情况下无法使用

        这意味着这个特定的编译器实例在某些情况下需要一个定义。即使它编译了,它也不会做任何事情。

        【讨论】:

        • 这是一个提炼样本,询问观察到的特定行为。我对使它成为一个完整的程序不感兴趣,我有兴趣了解它为什么在观察到的情况下构建和不构建。
        • 由于编译器特定的特性,它无法构建。它可能应该有,因为您从不破坏 X,并且它也可能只是删除了所有代码,因为它从未被调用过。但是,它没有。写的类没有意义,不编译也没关系。
        【解决方案6】:

        我怀疑这是实现定义的行为。这就是为什么

        $10.3/8- "声明了一个虚函数 在一个类中应定义,或 在该类中声明为纯(10.4),或 两个都;但不需要诊断 (3.2)。”

        GCC 给出了如下错误,这再次(至少对我而言)高度暗示了实现虚函数的非标准实现细节

        /home/OyXDcE/ccS7g3Vl.o:在函数中 X::X()': prog.cpp:(.text+0x6): undefined reference tovtable for X' /home/OyXDcE/ccS7g3Vl.o:在函数中 X::X()': prog.cpp:(.text+0x16): undefined reference tovtable for X' collect2: ld 返回 1 个退出状态

        如果真的需要编译器对 OP 代码进行诊断,我感到很困惑,所以考虑发布这个,即使我冒着投反对票的风险:)。当然,我猜应该是一个好的编译器。

        【讨论】:

          【解决方案7】:

          您可能会侥幸逃脱,因为 constr 和 destr 都是私有的 - 如果您的构建中没有对类 X 的其他引用,那么编译器可能会推断 destr 不是必需的,因此没有定义大佬。

          这并没有向我解释为什么案例 1 失败而 2 和 3 构建正常。想知道如果两者都公开会发生什么?

          【讨论】:

          • 我看到 Martin York 在上面已经完美地解释了这一点。
          猜你喜欢
          • 2011-02-04
          • 2011-12-24
          • 2015-11-12
          • 2016-12-28
          • 1970-01-01
          • 1970-01-01
          • 2011-04-17
          • 2014-11-04
          • 1970-01-01
          相关资源
          最近更新 更多