我想知道是否有任何 C++ 大师可以对这种奇怪的情况有所了解。Box2D 物理引擎附带的一个示例是崩溃并显示消息“已调用纯虚拟方法”,但仅限于特定编译器(并且仅在发布版本中)。
您可能知道 Box2D 是一段非常可靠的代码,所以我认为这可能是编译器的问题,特别是考虑到它只发生在这个特定的编译器上。我在 Windows7 上使用 mingw32:
> gcc.exe --version
gcc version 4.4.0 (GCC)
以下是 Box2D 相关部分的精简摘录。您可以在以下位置查看完整的源代码:
b2Shape.h
b2CircleShape.h
b2CircleShape.cpp
SensorTest.h
//base class
class b2Shape
{
public:
virtual ~b2Shape() {}
virtual b2Shape* Clone(b2BlockAllocator* allocator) const = 0;
};
//sub class
class b2CircleShape : public b2Shape
{
public:
b2CircleShape();
b2Shape* Clone(b2BlockAllocator* allocator) const;
};
inline b2CircleShape::b2CircleShape() {}
b2Shape* b2CircleShape::Clone(b2BlockAllocator* allocator) const
{
void* mem = allocator->Allocate(sizeof(b2CircleShape));
b2CircleShape* clone = new (mem) b2CircleShape;
*clone = *this;
return clone;
}
注意克隆功能中的新位置。
现在导致问题的执行归结为:
{
b2CircleShape shape;
shape.Clone(allocator); //ok
}
{
b2CircleShape shape;
shape.Clone(allocator); //"pure virtual method called"
}
在教育自己如何首先调用虚方法之后,我试图弄清楚它为什么会在这里发生,因为它不符合在基类构造函数中调用虚函数的经典案例。经过长时间的盲目摸索,我想出了上面的最小案例。
我的疯狂猜测是编译器足够聪明,可以看到这两个 b2CircleShape 实例没有在同一范围内使用,因此它只为一个分配空间并重用它。在第一个实例被破坏后,vtable 就像预期的那样,被冲洗掉了。然后由于某种原因,在构造第二个实例时,没有再次构造 vtable ......?
我想出了两件事来避免这个问题,但就像我说的那样,它看起来更像是一个编译器问题,所以我并不建议需要更改这段代码。
可疑的修复编号 1 是注释掉基类中的虚拟析构函数定义。我读过的关于这个主题的所有信息都表明这不是答案。(有趣的是,我发现仅仅从基类析构函数中删除 'virtual' 修饰符是不够的。我的理解是编译器会提供一个默认析构函数 ~b2Shape() {} 如果没有指定,那么为什么结果不同如果我真的指定默认值是什么?好吧,这真的是无关紧要的......)
我发现的不那么可疑的第二个修复是从子类构造函数中删除“内联”。也许在同一堆栈框架中放置新的内联构造和重用的实例不能很好地协同工作。(更新:进一步检查显示新的位置无关紧要)
更多的研究告诉我,编译器可以自由地对“内联”建议做任何事情,所以其他编译器可能没有这个问题,因为他们忽略了“内联”?