【问题标题】:Why virtual function can't be unimplemented when allocated with 'new'?为什么用'new'分配时不能未实现虚函数?
【发布时间】:2011-09-07 23:48:45
【问题描述】:
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'

【问题讨论】:

  • 我很震惊!如果是在栈上分配的,可以吗? O.O
  • 堆栈分配也不起作用(“A obj;”),你确定这是“// ok”吗?
  • 这段代码是假的:如果没有对malloc的结果进行显式转换,它将无法编译。
  • 我认为声明编译器优化标志很重要。使用 -O4,gcc 编译“A obj;”但不是“新A”;与 -O0 都失败。
  • @AndreyT:您一直将帖子和代码称为“假”,就好像有人试图将某些东西拉到您身上一样。 “假”代码和“坏”代码是有区别的。

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


【解决方案1】:

因为 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
}

【讨论】:

  • @Neil Butterworth - 尼尔你回来了? meta.stackexchange.com/questions/61077/…
  • 堆栈分配也调用构造函数,所以这并不能解释为什么“A obj;”应该可以工作,但“新 A”不应该。
  • @Chris A.:codepad.org 使用 GCC,它不会为 A obj 产生链接器错误。当然,这可能是优化设置的问题。
  • @Cris A. 无论是否调用new,该程序都是格式错误的。该标准要求在程序中定义所有使用函数 §3.2/3 每个程序都应包含该程序中使用的每个非内联函数或对象的一个​​定义;无需诊断。 .对于虚函数,used 的定义是 §3.2/2 如果它不是纯的,则使用虚成员函数。。您的编译器在堆栈中分配变量时没有抱怨这一事实属于规则的 no diagnostics required 位。也许这应该是一个答案......
  • @Chris 在高优化级别,代码不会产生错误,因为它会全部被优化掉,因为 A 实例实际上从未用于任何事情。在这些级别上,main 变成了一个空函数。
【解决方案2】:

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

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

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

【讨论】:

  • 你的意思是obj.bar() 对吧?顺便说一句,+1 可以很好地解释一个复杂的问题。
  • @Chris A.:我的意思是p-&gt;bar()。谢谢你指出来。固定。
  • 似乎链接器错误与编译器有关。以下不会为 A obj; ideone.com/aYf5W 提供错误
  • 并且我的代码不是 NOT FAKE (正如您在 cmets 中提到的问题)。 “错误”有很多礼貌用语。
  • @iammilind:毫无疑问,它依赖于编译器。从语言的角度来看,您的代码无效。之后,您将受编译器的支配。这总是依赖于编译器的。什么会触发错误,什么不会触发可能不容易预测。您制作的示例通过了编译,因为它不依赖于 VMT(编译器直接调用该函数)。总会有一些解释为什么它编译(当它编译时),但无论如何这都是浪费时间。代码无效。
【解决方案3】:

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

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

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

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

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

【讨论】:

    【解决方案4】:

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

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

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

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

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

    【讨论】:

      【解决方案5】:

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

      您的自动存储持续时间对象(您选择称为“堆栈上”的对象)未[多态地]使用,因此您无法获得诊断。这样做是不对的。

      【讨论】:

      • 我有一些使virtual 方法未实现的用例,这就是我遇到这种情况的原因。问题是为什么语言不允许这样做。
      • Un-used-ness 从编译器的角度来看是相关的。该程序格式不正确,并且符合标准的编译器不需要诊断(在任何一种情况下!)。此外,原始代码没有任何静态存储持续时间对象,而是自动存储。要成为 static,必须使用 static 关键字(或者变量必须位于命名空间级别)
      • @David:糟糕,我的意思是“自动”。而且我认为您已经向后阅读了我的“无关紧要”:正如您所说,它肯定与编译器有关。也就是说,在考虑规则的例外情况时,它是无关的。正如我所说,定义所有虚函数。
      猜你喜欢
      • 2011-03-04
      • 1970-01-01
      • 1970-01-01
      • 2014-02-02
      • 2016-12-22
      • 1970-01-01
      • 2013-06-24
      • 2017-01-22
      • 1970-01-01
      相关资源
      最近更新 更多