8
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但是对于使用(not )在堆上的分配malloc,它会给出链接器错误:

undefined reference to `vtable for A'
4

5 回答 5

9

因为 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
}
于 2011-06-03T17:13:31.303 回答
6

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

其次,与 的例子malloc完全无关。malloc分配原始内存,与任何特定类型绝对无关。在这种情况下,malloc知道注意 anyA并且它不会创建类型的对象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++ 语言无关。这只是特定于您的编译器的实现问题。

于 2011-06-03T17:41:06.767 回答
3

你不能留下一个未实现的虚函数,即使它是“未使用的”(因为它实际上是由 vtable 使用的)。这是代码中的错误。

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

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

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

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

于 2011-06-03T17:23:01.473 回答
1

该标准只要求在程序的任何地方实例化A::fooif的一种实现。A不管实例化是通过局部变量的声明还是通过新的表达式。但是,如果违反此规则,则不需要诊断;如果你没有提供声明,或者你提供了两个或更多,那只是未定义的行为。编译器所做的任何事情都是“正确的”。在这种情况下,它可能发生的是:

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

  • 由于该对象的所有使用对编译器都是可见的,因此它可以看到从未使用过 vptr,因此它只是简单地抑制它。

  • 并且没有vptr,就不需要生成vtable,所以没有对虚函数的引用。

总之,这取决于编译器如何优化;对于本地声明和新表达式,您可能会收到错误,或者两者都没有,或者其中一个而不是另一个。它可能取决于优化选项或其他什么。就 C++ 而言,它可能取决于月相,而不是错误,您可能只是得到在运行时崩溃的代码(但我首先说明的情况最有可能)。

于 2011-06-03T18:26:36.003 回答
0

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

您的自动存储持续时间对象(您选择将其称为“堆栈上”的对象)未使用 [多态性],因此您无法获得诊断。那并不正确。

于 2011-06-04T02:03:38.953 回答