提问人:Greg D 提问时间:8/25/2010 最后编辑:RakibGreg D 更新时间:8/4/2014 访问量:15603
为什么此虚拟析构函数会触发未解析的外部析构函数?
Why does this virtual destructor trigger an unresolved external?
问:
请考虑以下几点:
在 X.h 中:
class X
{
X();
virtual ~X();
};
X.cpp:
#include "X.h"
X::X()
{}
尝试构建这个(我正在使用 .dll 目标以避免在缺少主节点上出错,并且我正在使用 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 不是内联的时,什么原因导致链接器中出现未解析的外部?
编辑:
或者,也许更有趣的是,如果我使析构函数成为非虚拟的,或者如果我内联构造函数,为什么我不会得到未解析的外部函数?
答:
您需要为虚拟析构函数提供一个主体:
class X
{
X();
virtual ~X() {}
};
评论
这些还不是一个完整的程序(甚至不是一个完整的DLL)。当你得到错误时,你实际上是在得到帮助,因为如果没有 ~X() 的定义,X 是无法使用的
这意味着在某些情况下,这个特定的编译器实例需要一个定义。即使它编译了,它也不会做任何事情。
评论
你可能会侥幸逃脱,因为 constr 和 destr 都是私有的 - 如果你的构建中没有其他对类 X 的引用,那么编译器可能会推断出 destr 不是必需的,所以缺乏定义没什么大不了的。
这并不能向我解释为什么案例 1 失败,而案例 2 和 3 构建正常。想知道如果两者都公开会发生什么?
评论
情况 1:
您有构造函数的代码。
因此,它将构造函数构建到对象文件中。构造函数需要将析构函数的地址放到虚表中,因为它找不到它,构造函数无法构建。
情况 2:(内联构造函数)
编译器决定它不需要构建构造函数(因为它将被内联)。
因此,它不会植入任何代码,因此不需要析构函数的地址。
如果实例化 X 类型的对象,它将再次报错。
情况 3:(非虚拟析构函数)
不需要析构函数的地址来生成构造函数。
所以它不抱怨。
如果实例化 X 类型的对象,它将报错。
第一个问题的答案,
是什么原因导致未解决的外部 .dtor 为虚拟时的链接器 或者当 .ctor 没有内联时?
...很简单,就是你没有析构函数的定义。
现在你的第二个问题有点有趣:
为什么我没有得到解决 外部,如果我制作析构函数 非虚拟,或者如果我内联 构造 函数?
原因是因为你的编译器不需要析构函数,因为你从未实例化过,所以它把你的整个类都扔掉了。如果你尝试编译这个程序,你会得到一个未解析的外部:X
X
class X
{
public:
X();
~X();
};
X::X() {};
int main()
{
X x;
return 0;
}
但是,如果您注释掉它,它将编译得很好,正如您所观察到的。X x;
现在让我们回过头来谈谈为什么它不会编译 if 析构函数 if .我在这里推测,但我相信原因是因为,因为你有一个虚拟析构函数,现在是一个多态类。为了在内存中布局多态类,使用 vtable 实现多态的编译器需要每个虚拟函数的地址。您尚未实现,因此未解决的外部结果。virtual
X
X::~X
为什么编译器不像不是多态类时那样扔掉呢?更多猜测在这里。但我想原因是因为即使你没有直接实例化,也不能确定你的代码中没有任何地方会像其他东西一样进行实时、混淆。例如,考虑一个抽象基类。在这种情况下,您永远不会直接实例化,并且代码可能位于完全独立的翻译单元中。因此,当编译器到达这个多态类时,即使它不知道你实例化了它,它也无法丢弃它。X
X
X
X
Base
Derived
在 C++ 中,当且仅当函数在程序中使用时,必须定义函数(参见 3.2/2 中的 ODR)。通常,如果从可能计算的表达式调用非虚函数,则使用非虚函数。任何非纯虚函数都被视为无条件使用。当使用[非虚拟]特殊成员时,在语言标准的专用位置定义函数。等等。
在第一个示例中,您将析构函数声明为非纯虚函数。这立即意味着您的析构函数已在程序中使用。这反过来又意味着需要该析构函数的定义。您未能提供定义,因此编译器报告了错误。
在第三个示例中,析构函数是非虚拟的。由于您没有在程序中使用析构函数,因此不需要定义,代码会编译(有关使用析构函数的详细说明,请参见 12.4)。
在第二个示例中,您正在处理实现的一个怪癖,该怪癖是由构造函数内联的事实触发的。由于析构函数是非纯虚拟的,因此定义是必需的。但是,编译器未能检测到错误,这就是代码似乎编译成功的原因。您可以在实现的细节中挖掘此行为的原因,但从 C++ 的角度来看,由于完全相同的原因,此示例与第一个示例一样损坏。
我对此表示怀疑,这是实现定义的行为。原因如下
$10.3/8- “声明了一个虚拟函数 在类中应定义,或 在该类中声明为 pure (10.4),或 双;但不需要诊断 (3.2)."
GCC 给出了如下错误,这再次高度暗示了(至少对我而言)关于实现虚函数的非标准实现细节
/home/OyXDcE/ccS7g3Vl.o:在函数 vtable for X' /home/OyXDcE/ccS7g3Vl.o:在函数 vtable for X' collect2:ld 返回 1 退出状态
X::X()': prog.cpp:(.text+0x6): undefined reference to
X::X()': prog.cpp:(.text+0x16): undefined reference to
我很困惑是否真的需要编译器对 OP 代码进行诊断,所以想到发布这个,即使我冒着投反对票的风险:)。当然,我应该猜一个好的编译器。
评论