为什么在使用'new'分配时无法实现虚函数?

时间:2011-06-03 17:12:50

标签: c++ linker-errors language-lawyer virtual-method

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
}

对于堆栈it works fine上的对象。但是对于使用new(而不是malloc)在堆上进行分配,它会给出链接器错误:

undefined reference to `vtable for A'

5 个答案:

答案 0 :(得分: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
}

答案 1 :(得分:6)

首先,此代码不可编译,因为在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
}

问题是为什么第一个声明不产生liker错误而第二个声明产生错误。

从形式上看,您的程序无效,因为它违反了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 ++语言无关。这只是编译器特有的实现问题。

答案 2 :(得分:3)

你不能保持虚函数未实现,即使它是'未使用'(因为它实际上是由vtable使用的)。这是代码中的错误。

由于编译器中vtable的特殊实现,bug以这种特殊的方式表现出来。你没有实现第一个虚函数。只要看到类的第一个虚函数的实现,编译器就会插入vtable。由于没有,所以没有vtable。

如果你没有实现第二个功能,链接器会抱怨该特定功能,而不是关于vtable。

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

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

答案 3 :(得分:1)

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

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

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

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

总之,它取决于编译器如何优化;你可能会收到一个错误 对于本地声明和新表达,或两者都没有, 或一个而不是另一个。它可能取决于优化 选项,或其他。就C ++而言,它可能依赖于 月亮的阶段,而不是一个错误,你可能只是得到 运行它时崩溃的代码(但我先说的是场景 最有可能的。)

答案 4 :(得分:0)

未使用无关紧要。定义所有虚拟功能。就这么简单。

您的自动存储持续时间对象(您选择在堆栈上调用对象的内容)不会[多态]使用,因此您无法进行诊断。这不是正确的。