15

考虑以下:

Xh 中:

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

X.cpp:

#include "X.h"

X::X()
{}

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

错误 1 ​​错误 LNK2001:未解析的外部符号“私有:虚拟 __thiscall X::~X(void)”(??1X@@EAE@XZ)

但是,小的修改会导致构建成功:

Xh:

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

或者

Xh:

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

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

编辑:

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

4

7 回答 7

23

情况一:

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

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

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

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

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

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

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

于 2010-08-24T20:54:07.050 回答
7

您需要为虚拟析构函数提供一个主体:


class X
{
    X();
    virtual ~X() {}
};
于 2010-08-24T20:37:43.203 回答
5

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

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

  • 在您的第三个示例中,析构函数是非虚拟的。由于您没有在程序中使用析构函数,因此不需要定义并且代码可以编译(有关使用析构函数的详细说明,请参见第 12.4 节)。

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

于 2010-08-24T21:06:17.933 回答
2

第一个问题的答案,

当 .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可能位于完全独立的翻译单元中。所以当编译器得到这个多态类时,即使它不知道你实例化了它,它也不能丢弃它。

于 2010-08-24T21:04:01.510 回答
1

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

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

于 2010-08-24T20:38:18.710 回答
1

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

$10.3/8-“在一个类中声明的虚函数应在该类中定义或声明为纯函数(10.4),或两者兼而有之;但不需要诊断(3.2)。”

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

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

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

于 2010-08-25T02:41:04.387 回答
0

您可能会逃避这一点,因为 constr 和 destr 都是私有的 - 如果您的构建中没有对类 X 的其他引用,那么编译器可能会推断 destr 不是必需的,因此缺少定义并不是什么大问题。

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

于 2010-08-24T20:53:26.867 回答