为什么用“new”分配虚函数时不能取消实现?
struct A
{
virtual void foo(); // unused and unimplemented
virtual void bar () {}
};
int main ()
{
A obj; // ok
obj.bar(); // <-- added this edition
A* pm = (A*)malloc(sizeof(A)); // ok
A* pn = new A; // linker error
}
对于堆栈上的对象它工作正常。但是对于使用 new
(而不是 malloc
)在堆上分配,它会给出链接器错误:
undefined reference to `vtable for A'
struct A
{
virtual void foo(); // unused and unimplemented
virtual void bar () {}
};
int main ()
{
A obj; // ok
obj.bar(); // <-- added this edition
A* pm = (A*)malloc(sizeof(A)); // ok
A* pn = new A; // linker error
}
For objects on stack it works fine. But for allocation on heap with new
(not malloc
), it gives linker error:
undefined reference to `vtable for A'
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
因为 malloc 不会调用(或在本例中尝试调用)A 的构造函数,而 new 会调用。
此代码编译并记录 GCC 发生链接器错误的位置:
Because malloc does not call (or attempt to call in this case) A's constructor, whereas new does.
This code compiles and notes where linker errors occur with GCC:
首先,此代码不可编译,因为在 C++ 中
void *
无法隐式转换为A *
。需要显式强制转换。其次,
malloc
的示例完全不相关。 malloc 分配原始内存,与任何特定类型完全没有关系。在这种情况下,malloc
知道任何A
,并且它不会创建A
类型的对象。由于这个原因,这个问题的真实例子应该如下所示
,问题是为什么第一个声明不会产生 liker 错误,而第二个声明会产生类似错误。
从形式化的角度来看,你的程序是无效的,因为它违反了C++语言的形式化要求(特别是ODR)。实际上,这两种声明可能或应该产生相同的错误,因为在这两种情况下,对象形式上都需要一个指向 VMT 的指针。在这种情况下,无法创建 VMT,因为某些函数未定义。然而,第一个声明只是因为编译器能够优化第一个声明(而不是第二个声明)对 VMT 的所有引用而被忽略。编译器也很可能能够优化整个 obj 对象,因为它没有在其他地方被引用。
在 GCC 中(因为您似乎正在使用 GCC),第一个声明也很容易触发相同的错误。
即使未定义的函数
foo
,上述代码也会在 GCC 中产生相同的链接器错误这段代码中仍然没有使用。换句话说,只需添加足够数量的代码以使编译器相信需要对象的 VMT 即可。在这种情况下,声明之间的行为差异与 C++ 语言无关。这只是您的编译器特有的实现问题。
Firstly, this code is not compilable, since in C++
void *
cannot be implicitly converted toA *
. An explicit cast is required.Secondly, the example with
malloc
is completely irrelevant.malloc
allocates raw memory, with has absolutely no relation to any specific types. In this casemalloc
knows noting about anyA
and it does not create an object of typeA
.For this reasons the real example for this question should look as follows
And the question is why the first declaration produces no liker error while the second one does.
From the formal point of view, your program is invalid because it violates formal requirements of C++ language (specifically ODR). In practice, both declarations could or should produce the same error, since in both cases the object formally requires a pointer to VMT. In this case VMT cannot be created, since some functions are undefined. However, the first declaration simply slips through just because the compiler was able to optimize-out all references to VMT for the first declaration (and not for the second). It is also quite possible that the compiler was able to optimize-out the whole
obj
object, since it is not referenced anywhere else.In GCC (since you appear to be using GCC) it is easy to trigger the same error for the first declaration as well
The above code will produce the same linker error in GCC even though the undefined function
foo
is still not used in this code.In other words, it is simply a matter of adding sufficient amount of code to make the compiler believe that the object's VMT is needed. The difference in behavior between the declarations has nothing to do with C++ language in this case. It is just an implementation issue specific to your compiler.
你不能让虚拟函数未实现,即使它“未使用”(因为它实际上被虚函数表使用)。这是代码中的错误。
由于编译器中 vtable 的特殊实现,该错误以这种特殊方式显现出来。您尚未实现第一个虚拟函数。每当编译器看到类的第一个虚函数的实现时,它就会插入 vtable。因为没有,所以也就没有 vtable。
如果您未实现第二个函数,链接器将抱怨该特定函数,而不是 vtable。
[编辑]
您的编译器可能优化了堆栈上的
A
副本,这就是链接器没有抱怨的原因。malloc
行实际上并不引用 A 类型的对象,这就是它不会产生链接器问题的原因。不过,这一行还有另一个问题:它不应该编译。malloc
返回void*
,如果不进行强制转换,它不会转换为其他类型的指针。You cannot leave a virtual function unimplemented, even if it's 'unused' (because it's in fact used by the vtable). This is the bug in the code.
The bug manifests itself in this particular fashion because of a peculiar implementation of vtables in the compiler. You have left unimplemented the first virtual function. The compiler inserts the vtable whenever it sees the implementation of the first virtual function of the class. Since there isn't any, there's no vtable.
If you leave the second function unimplemented, the linker will complain about that specific function, not about the vtable.
[edit]
Your compiler probably optimized-out a copy of
A
on the stack, that's the reason the linker didn't complain.The
malloc
line doesn't actually reference an object of type A, that's why it doesn't create a linker problem. There's another problem with this line though: it should not compile.malloc
returnsvoid*
which does not convert to other types of pointer without a cast.如果
A
是,该标准只需要A::foo
的一个实现在程序中的任何位置实例化。无论是否
实例化是通过声明局部变量或通过
一个新的表达方式。但是,如果此规则适用,则无需诊断
破碎的;如果您没有提供声明,或者您提供了两个或更多声明,
这只是未定义的行为。编译器所做的任何事情都是
“正确的”。在这种情况下,可能发生的情况是:
A
的构造函数是内联的,因此初始化 vptr 的代码(并触发 vtable 的实例化)对编译器完全可见,由于该对象的所有使用对编译器都是可见的,因此它可以看到 vptr 从未被使用,因此它只是抑制它。
并且没有vptr,不需要生成vtable,因此没有对虚函数的引用。
总而言之,取决于编译器如何优化;你可能会得到一个错误
对于本地声明和 new 表达式,或两者都不是,
或者只为其中之一而不为另一个。这可能取决于优化
选项,或者其他什么。就 C++ 而言,它可能取决于
月相,你可能会得到而不是错误
当你运行它时崩溃的代码(但我首先提到的场景是
最有可能)。
The standard requires exactly one implementation of
A::foo
ifA
isinstantiated anywhere in the program. Regardless of whether the
instantiation is through the declaration of a local variable or through
a new expression. However, no diagnostic is required if this rule is
broken; if you provide no declaration, or if you provide two or more,
it's simply undefined behavior. Anything the compiler does is
"correct". In this case, what it probably happening is:
the constructor of
A
is inline, so the code which initializes the vptr (and triggers the instantiation of the vtable) is fully visible to the compiler,since all uses of the object are visible to the compiler, it can see that the vptr is never used, so it simply suppresses it.
and with no vptr, no vtable need be generated, so there is no reference to the virtual function.
In sum, it depends on how the compiler optimizes; you might get an error
for both the local declaration and the new expression, or for neither,
or for one and not the other. And it might depend on the optimization
options, or whatever. As far as C++ is concerned, it might depend on
the phases of the moon, and instead of an error, you might simply get
code which crashed when you ran it (but the scenarios I stated first are
the most likely).
不使用是无关紧要的。定义所有虚拟函数。就这么简单。
您的自动存储持续时间对象(您选择将对象称为“堆栈上”的对象)不会[多态]使用,因此您不会得到任何诊断。这并不能说明问题。
Unusedness is irrelevant. Define all virtual functions. It's as simple as that.
Your automatic storage duration object (what you've chosen to call an object "on the stack") is not used [polymorphically], so you get no diagnostic. That doesn't make it right.