为什么这个虚拟析构函数会触发一个未解析的外部?
考虑以下内容:
在 Xh:
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)
小修改会导致成功构建,但是:
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 未内联时,是什么导致链接器中无法解析外部?
编辑:
或者,也许更有趣的是,如果我将析构函数设置为非虚拟的,或者如果我内联构造函数,为什么我不会得到未解析的外部?
Consider the following:
In X.h:
class X
{
X();
virtual ~X();
};
X.cpp:
#include "X.h"
X::X()
{}
Try to build this (I'm using a .dll target to avoid an error on the missing main, and I'm using Visual Studio 2010):
Error 1 error LNK2001: unresolved external symbol "private: virtual __thiscall X::~X(void)" (??1X@@EAE@XZ)
Small modifications result in a successful build, however:
X.h:
class X
{
inline X(); // Now inlined, and everything builds
virtual ~X();
};
or
X.h:
class X
{
X();
~X(); // No longer virtual, and everything builds
};
What causes the unresolved external in the linker when the .dtor is virtual or when the .ctor isn't inlined?
EDIT:
Or, perhaps more interestingly, why do I not get an unresolved external if I make the destructor non-virtual, or if I inline the constructor?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
情况 1:
您拥有构造函数的代码。
因此它将构造函数构建到目标文件中。构造函数需要将析构函数的地址放入虚拟表中,因为找不到它,构造函数无法构建。
情况 2:(内联构造函数)
编译器决定不需要构建构造函数(因为它将被内联)。
因此,它不会植入任何代码,因此不需要析构函数的地址。
如果你实例化一个 X 类型的对象,它会再次抱怨。
情况3:(非虚析构函数)
构建构造函数不需要析构函数的地址。
所以它不抱怨。
如果你实例化一个 X 类型的对象,它会抱怨。
Situation 1:
You have the code for the constructor.
So it builds the constructor into the object file. The constructor needs the address of the destructor to put into the virtual table because it can not find it the constructor can not be built.
Situation 2: (inline constructor)
The compiler decides it does not need to build the constructor (as it will be inlined).
As such it does not plant any code and therefore does not need the address of the destructor.
If you instanciate an object of type X it will again complain.
Situation 3: (non virtual destructor)
You don't need the address of the destructor to build the constructor.
So it does not complain.
It will complain if you instantiate an object of type X.
您需要为虚拟析构函数提供一个主体:
You need to give a body to the virtual destructor:
在 C++ 中,当且仅当在程序中使用函数时,才必须定义函数(请参阅 3.2/2 中的 ODR)。一般来说,如果从可能计算的表达式中调用非虚函数,就会使用。任何非纯虚函数都被视为无条件使用。当使用[非虚拟]特殊成员函数时,使用是在语言标准的专用位置定义的。等等。
在您的第一个示例中,您将析构函数声明为非纯虚函数。这立即意味着您的程序中使用了您的析构函数。反过来,这意味着需要该析构函数的定义。您未能提供定义,因此编译器报告了错误。
在您的第三个示例中,析构函数是非虚拟的。由于您没有在程序中使用析构函数,因此不需要定义并且代码可以编译(有关析构函数使用的详细描述,请参阅 12.4)。< /p>
在您的第二个示例中,您正在处理实现的一个怪癖,这是由构造函数内联这一事实触发的。由于析构函数是非纯虚拟的,因此需要定义。但是,您的编译器未能检测到该错误,这就是代码似乎编译成功的原因。您可以在实现的细节中挖掘此行为的原因,但从 C++ 的角度来看,此示例与第一个示例一样损坏,原因完全相同。
In C++ functions have to be defined if and only if they are used in your program (see the ODR in 3.2/2). In general, non-virtual functions are used if they are called from potentially evaluated expressions. Any non-pure virtual function is considered unconditionally used. When [non-virtual] special member functions are used is defined in dedicated locations of the language standard. And so on.
In your first example, you declared your destructor as a non-pure virtual function. This immediately means that your destructor is used in your program. This, in turn, means that the definition of that destructor is required. You failed to provide a definition, so the compiler reported an error.
In your third example the destructor is non-virtual. Since you are not using the destructor in your program, no definition is required and the code compiles (see 12.4 for detailed description of what constitutes a use of a destructor).
In your second example you are dealing with a quirk of the implementation, triggered by the fact that the constructor is inlined. Since the destructor is non-pure virtual, the definition is required. However, your compiler failed to detect the error, which is why the code seems to compile successfully. You can dig for the reasons of this behavior in the details of the implementation, but from the C++ point of view this example is as broken as the first one for exactly the same reason.
你第一个问题的答案,
...很简单,您没有析构函数的定义。
现在你的第二个问题更有趣:
原因是因为你的编译器不需要 X 的析构函数,因为你从未实例化 X,所以它抛弃了你的整个类。如果您尝试编译此程序,您将得到一个未解析的外部:
但如果您注释掉
X x;
,正如您所观察到的那样,它会编译得很好。现在让我们回过头来看看为什么如果析构函数是
virtual
,它就无法编译。我在这里推测,但我相信原因是因为,由于您有一个虚拟析构函数,所以X
现在是一个多态类。为了在内存中布局多态类,使用 vtable 需要每个虚拟函数的地址。您尚未实现X::~X
,因此无法解析外部结果。为什么编译器不像
X
不是多态类时那样直接丢弃X
呢?这里有更多猜测。但我认为原因是因为即使您没有直接实例化X
,也无法确定代码中没有X
处于活动状态,伪装成某种东西别的。例如,考虑一个抽象基类。在这种情况下,您永远不会直接实例化Base
,并且Derived
的代码可能位于完全独立的翻译单元中。因此,当编译器到达这个多态类时,即使它不知道您实例化了它,它也无法丢弃它。The answer to your first question,
...is, quite simply, that you don't have a definition for the destructor.
Now your second question is somewhat more interesting:
And the reason is because your compiler didn't need
X
's destructor since you never instantiatedX
, so it threw your whole class away. If you try to compile this program, you will get an unresolved external:But if you comment out
X x;
it will compile just fine, as you have observed.Now let's come back around to why it won't compile if the destructor if
virtual
. I'm speculating here, but I believe the reason is because, since you have a virtual destructor,X
is now a polymorphic class. In order to lay-out polymorphic classes in memory, compilers that implement polymorphism using a vtable need the adresses to every virtual function. You haven't implementedX::~X
, so an unresolved external results.Why doesn't the compiler just throw
X
away as it did whenX
was not a polymorphic class? More speculation here. But I expect the reason is because even if you haven't directly instantiatedX
, it can't be sure that nowhere in your code does anX
live, masqerading as something else. For an example, consider an abstract base class. In this case, you'll never instantiateBase
directly and the code forDerived
might be in a totally seperate translation unit. So when the compiler gets to this polymorphic class, it can't discard it even if it doesn't know you instantiated it.这些还不是一个完整的程序(甚至不是一个完整的 DLL)。当您收到错误时,您实际上得到了帮助,因为如果没有 ~X() 的定义,X 就无法使用。
这意味着这个特定的编译器实例在某些情况下需要它的定义。即使编译通过,它也没有做任何事情。
These aren't a complete program yet (or even a complete DLL). When you are getting the error, you are actually being helped, because X is unusable without a definition for ~X()
All it means is that this specific compiler instance needed a definition for it in some cases. Even if it compiles, it doesn't do anything.
我怀疑这是实现定义的行为。这就是为什么
GCC 给出如下错误,这再次强烈暗示(至少对我来说)关于实现虚拟函数的非标准实现细节
我很困惑OP代码是否真的需要编译器进行诊断,所以考虑发布这个,即使我冒着投反对票的风险:)。当然,我猜应该是一个好的编译器。
I have a suspicion on this one that this is implementation defined behavior. Here's why
GCC gives error such as below, which again, is highly suggestive (to me at least) about a non-standard implementation detail of implementing virtual functions
I am confused if a diagnostic is really required from a compiler for the OP code, so thought of posting this, even as I risk downvotes :). Of course, a good compiler should I guess.
您可能会逃脱这一点,因为 constr 和 destr 都是私有的 - 如果您的构建中没有对类 X 的其他引用,那么编译器可能会推断出 destr 不是必需的,因此缺少定义并不是什么大问题。
这并不能向我解释为什么情况 1 失败,而情况 2 和 3 构建正常。想知道如果两者都公开会发生什么吗?
You may be getting away with this because both constr and destr are private - if there is no other ref to class X in your build then the compiler may be deducing that the destr is not required, so lack of a definition is no biggie.
This does not explain to me why case 1 fails while 2 and 3 build OK though. Wonder what happens if both are made public?