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

iam*_*ind 8 c++ virtual-functions linker-errors language-lawyer

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
}
Run Code Online (Sandbox Code Playgroud)

对于堆栈上的对象,它工作正常.但是对于堆上的分配new(不malloc),它会给出链接器错误:

undefined reference to `vtable for A'
Run Code Online (Sandbox Code Playgroud)

小智 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
}
Run Code Online (Sandbox Code Playgroud)

  • @Cris A.无论是否呼叫"新",这个节目都是格格不入.该标准要求所有*used*函数都在程序中定义§3.2/ 3*每个程序应该只包含该程序中使用的每个非内联函数或对象的一个​​定义; 无需诊断.*.对于虚函数,*used*的定义是§3.2/ 2*如果它不是纯粹的,则使用虚拟成员函数.*.当变量在堆栈中分配时,编译器没有抱怨的事实属于规则的*no diagnostics required*位.也许这应该是一个答案...... (4认同)
  • @Chris在高优化级别,代码不会产生任何错误,因为它们都将被优化掉,因为A实例从未真正用于任何事情.在这些级别,main变为空函数. (2认同)

AnT*_*AnT 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
}
Run Code Online (Sandbox Code Playgroud)

问题是为什么第一个声明不会产生任何错误,而第二个声明会产生错误.

从形式上看,您的程序无效,因为它违反了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(); 
}
Run Code Online (Sandbox Code Playgroud)

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

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