为什么这个虚拟析构函数会触发一个未解析的外部?

时间:2010-08-24 20:34:03

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

请考虑以下事项:

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没有内联时,导致链接器中未解析的外部的原因是什么?

修改

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

7 个答案:

答案 0 :(得分:21)

情况1:

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

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

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

如果您实例化X类型的对象,它将再次抱怨。

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

您不需要析构函数的地址来构建构造函数 所以它没有抱怨。

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

答案 1 :(得分:5)

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

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

  • 第三个示例中,析构函数是非虚拟的。由于您不是在程序中使用析构函数,因此无需定义并编译代码(有关构造析构函数的 use 的详细说明,请参阅12.4)。 / p>

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

答案 2 :(得分:4)

您需要为虚拟析构函数提供一个正文:


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

答案 3 :(得分:2)

第一个问题的答案,

  

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

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

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

  

为什么我没有得到解决   外部,如果我做析构函数   非虚拟的,或者如果我内联的话   构造

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

class X
{
public:
    X();
     ~X();
};

X::X() {};

int main()
{
    X x;
    return 0;
}

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

现在让我们回过头来看看为什么如果析构函数virtual将无法编译。我在这里推测,但我相信原因是因为你有一个虚拟析构函数,X现在是一个多态类。为了在内存中布置多态类,使用vtable实现多态的编译器需要每个虚函数的地址。您尚未实施X::~X,因此未解决的外部结果。

为什么编译器不会像X不是多态类那样抛出X?这里有更多猜测。但我希望原因是因为即使你没有直接实例化X,也不能确定代码中没有任何地方存在X直播,而是将其作为其他内容。例如,考虑一个抽象基类。在这种情况下,您永远不会直接实例化Base,而Derived的代码可能位于完全独立的翻译单元中。因此,当编译器进入这个多态类时,即使它不知道你实例化它也不能丢弃它。

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

  

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

GCC给出了如下错误,这对于实现虚拟功能的非标准实现细节再次提示(至少对我而言)

  

/home/OyXDcE/ccS7g3Vl.o:在功能上   X X::X()': prog.cpp:(.text+0x6): undefined reference to vtable   /home/OyXDcE/ccS7g3Vl.o:在功能上   X X::X()': prog.cpp:(.text+0x16): undefined reference to vtable   collect2:ld返回1退出状态

如果编译器确实需要对OP代码进行诊断,我很困惑,所以考虑发布这个,即使我冒险downvotes :)。当然,我应该猜一个好的编译器。

答案 6 :(得分:0)

你可能正在逃避这个因为constr和destr都是私有的 - 如果在你的构建中没有其他引用类X,那么编译器可能会推断出不需要destr,所以缺少定义就是no不算什么。

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