为什么虚拟功能在分配"new"时不能取消实现?

Why virtual function can't be unimplemented when allocated with 'new'?

本文关键字:不能 取消 实现 new 功能 分配 为什么 虚拟      更新时间:2023-10-16
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'

因为 malloc 不调用(或在这种情况下尝试调用(A 的构造函数,而 new 会调用。

此代码编译并记录 GCC 发生链接器错误的位置:

#include <cstdlib>
struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};
int main ()
{
  A obj;        // linker error
  A* pm = (A*) malloc(sizeof(A)); // ok
  A* pn = new A; // linker error
}

首先,此代码不可编译,因为在C++ void *中不能隐式转换为 A * 。需要显式强制转换。

其次,malloc的例子完全无关紧要。 malloc分配原始内存,与任何特定类型完全无关。在这种情况下,malloc知道注意任何A,并且它不会创建类型为 A 的对象。

出于这个原因,这个问题的真实例子应该如下所示

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};
int main ()
{
  A obj;        // ok
  A* pn = new A; // linker error
}

问题是为什么第一个声明没有产生更相似的错误,而第二个声明会产生。

从形式上的角度来看,你的程序是无效的,因为它违反了C++语言(特别是ODR(的形式要求。实际上,这两个声明可能或应该产生相同的错误,因为在这两种情况下,对象正式都需要指向 VMT 的指针。在这种情况下,无法创建 VMT,因为某些函数未定义。但是,第一个声明只是因为编译器能够优化第一个声明(而不是第二个声明(对 VMT 的所有引用。编译器也很有可能能够优化整个obj对象,因为它没有在其他任何地方被引用。

在 GCC 中(因为您似乎正在使用 GCC(,也很容易为第一个声明触发相同的错误

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};
int main ()
{
  A obj; // linker error
  A *p = &obj;
  p->bar(); 
}

上面的代码将在 GCC 中产生相同的链接器错误,即使此代码中仍未使用未定义的函数foo

换句话说,只需添加足够数量的代码即可使编译器认为需要对象的 VMT。在这种情况下,声明之间的行为差异与C++语言无关。这只是特定于编译器的实现问题。

你不能让虚拟函数未实现,即使它是"未使用的"(因为它实际上被 vtable 使用(。这是代码中的错误。

由于编译器中 vtables 的特殊实现,该错误以这种特定方式表现出来。您尚未实现第一个虚拟函数。每当编译器看到类的第一个虚函数的实现时,它都会插入 vtable。既然没有,就没有vtable。

如果未实现第二个函数,链接器将抱怨该特定函数,而不是 vtable。

[编辑]您的编译器可能优化了堆栈上A的副本,这就是链接器没有抱怨的原因。

malloc行实际上并不引用类型 A 的对象,这就是它不会产生链接器问题的原因。不过,这条线还有另一个问题:它不应该编译。 malloc返回不转换为没有强制转换的其他类型的指针的void*

A::foo如果A在程序中的任何位置实例化。 无论是否实例化是通过局部变量的声明或通过一个新的表达式。 但是,如果此规则是破碎;如果您不提供声明,或者如果您提供两个或更多声明,这只是未定义的行为。 编译器所做的任何事情都是"正确"。 在这种情况下,它可能发生的事情是:

  • 需要定义的原因是因为它在 vtable 中被引用,
  • A 的构造函数是内联的,因此初始化 VPTR(并触发 vtable 的实例化(的代码对编译器完全可见,

  • 由于对象的所有用途对编译器都是可见的,因此它可以看到 VPTR 从未被使用过,因此它只是抑制它。

  • 并且没有 VPTR,不需要生成 vtable,因此没有对虚函数的引用。

总之,这取决于编译器的优化方式;你可能会得到一个错误。对于本地声明和新表达式,或者两者都不是,或者对于一个而不是另一个。 这可能取决于优化选项,或者其他什么。 就C++而言,它可能取决于月相,而不是错误,你可能会得到运行它时崩溃的代码(但我首先陈述的场景是最有可能(。

未使用是无关紧要的。定义所有虚函数。就这么简单。

您的自动存储持续时间对象(您选择将对象称为"在堆栈上"(不会 [多态] 使用,因此您不会获得诊断。这不对。