如何解决“纯虚方法被调用”问题

31
我知道为什么会发生这种情况,但我卡在了解决它的过程中……以下是我的代码在程序退出时生成错误(因此导致崩溃)时正在执行的操作...
纯虚方法被调用。
SomeClass::~SomeClass()
{
   BaseClassObject->SomePureVirtualMethod(this);
}

void DerivedClass::SomePureVirtualMethod(SomeClass* obj)
{
    //Do stuff to remove obj from a collection
}

我从未调用new SomeClass,但我有一个QList<SomeClass*>,我会将SomeClass*对象附加到其中。在SomeClass中的析构函数的目的是告诉DerivedClass从其QList<SomeClass*>集合中删除特定实例的SomeClass

因此,以下是一个具体示例...

BaseClass = Shape

DerivedClass = Triangle

SomeClass = ShapeProperties,它拥有对Shape的引用

所以,我从未调用new ShapeProperties,但我在Triangle内部有一个QList<ShapeProperties*>。在ShapeProperties中的析构函数是为了告诉Triangle从其QList<ShapeProperties*>集合中删除ShapeProperties的特定属性。

3个回答

42
在调用对象的析构函数时,继承类的析构函数已经被调用了。在构造函数和析构函数内部,可以将对象的动态类型有效地视为静态类型。也就是说,在构造函数/析构函数中调用虚方法时,并不会调用其被覆盖的版本。
如果需要在析构函数中调用SomePureVirtualMethod,则必须在实际定义该方法的类的析构函数中调用它。

那么,如果我在派生构造函数中调用它,我怎么知道我正在引用哪个this实例? - user869525
我可能误解了这一点:“如果SomePureVirtualMethod需要在析构函数中被调用,则必须在实际定义您所需方法的类的析构函数中调用它。” - user869525
1
简单来说,你在析构函数中调用 SomePureVirtualMethod 方法时,不会解析到最终派生类的重载方法。如果你想调用最终派生类的重载方法,你必须在实现该重载方法的类的析构函数中调用它。 - K-ballo
是的,我理解这一点,但它并不能解决我的问题,因为我在 SomeClass 中,而不是在 BaseClassDerivedClass 中。SomeClass 拥有一个 BaseClass 的实例,而 DerivedClass 拥有一个 SomeClasslistSomeClass 中的析构函数需要从 DerivedClass 内部的 list 中删除一个 SomeClass 实例。 - user869525

14
当你在基类SomeClass的析构函数中调用virtual方法时,它会调用基类SomeClass的纯虚方法(SomePureVirtualMethod()),而这个方法没有定义,因此会出现错误。 为什么会发生这种情况?
在构造函数或析构函数中,this的类型是正在被调用的构造函数或析构函数的类型,因此动态分派在构造函数和析构函数中不起作用,就像在其他所有函数中一样。 为什么会崩溃?
因为从构造函数或析构函数中调用纯虚函数是一种未定义行为。 C++03 10.4/6规定

 

"可以从抽象类的构造函数(或析构函数)中调用成员函数;直接或间接地为正在创建(或销毁)的对象调用纯虚函数的虚拟调用(10.3),在这样的构造函数(或析构函数)中的效果是未定义的。"

如何避免这种情况?
只需确保您不从构造函数或析构函数中调用纯虚函数。
除非您了解所涉及的动态性,否则不要在构造函数或析构函数中调用virtual方法。

1

还有另一个可能导致这种情况发生的原因,取决于您的编译器和系统,即指针引用无效。Paul S. R. Chisholm 解释了内存释放后可能出现的状态:

  • 内存可能被标记为已释放。
  • 内存可能被故意混淆。
  • 内存可能被重复使用。
  • 内存可能被保持不变。

最后一种情况很有趣。这个对象被保留“原封不动”,是什么意思呢?在这种情况下,它是抽象基类的一个实例;当然虚函数表也是按照原样保留的。如果我们尝试为这样的对象调用一个纯虚成员函数会发生什么呢?

“调用纯虚函数”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接