了解“基础C / C ++对象已被删除”错误

时间:2011-05-14 15:41:02

标签: qt runtime pyqt

这不是我第一次获得RuntimeError: underlying C/C++ object has been deleted。我已经解决了很多次,以随机但直观的方式改变我的代码,但现在我再次面对这一点,只是不明白为什么会发生... 我要求的是面对和解决这个错误的通用方法。

我不会在这里发布代码示例,因为我的项目过于复杂,我无法弄清楚错误在哪里。而且因为我要求的通用解决方案不仅适用于这种情况。

为什么可以删除“基础C / C ++”对象? 怎么避免呢?
如何测试底层对象是否存在?

5 个答案:

答案 0 :(得分:13)

您无法测试基础对象是否存在(例如,是否已被“删除”)。你可以使用一些“技巧”来帮助这种“善意”,但我不推荐它。相反,恕我直言,我猜你的设计缺乏强大的所有权语义。 (这可能是必要的,具体取决于问题和您的域名的复杂程度,但如果可以避免,我建议不要这样做。)

您的问题是所有权语义。尤其是在C ++中,与使用内存“垃圾收集”的其他语言相比,你必须拥有一个对“谁拥有什么”有深刻理解的设计。您的设计应该让“简单明了”知道哪些对象存在,以及谁“拥有”它们。

一般来说,这意味着将对象的“创建”和“删除”集中到某种“父母”中,从来没有任何问题,“这是谁的对象?”和“你管理的对象是什么?”

例如,“Parent”类将分配它“需要”的成员对象,并且只有该父对象才会删除这些对象。父的析构函数确保删除这些成员,因此这些对象不会被意外地“留下”。因此,父“知道”其成员对象,当父对象消失时,您知道成员对象也已消失。如果您的设计是强耦合的,那么成员对象可以引用父对象,但这是所有权语义的“深层结束”(通常不推荐)。

有些设计可能非常复杂。但是,一般来说,所有权语义永远不应该是复杂的:你应该总是知道“谁拥有什么”,因此,你应该总是知道“存在什么对象”。

使用垃圾收集语言可以实现其他设计,其中对象可能是“自由代理”,并且您相信垃圾收集器可以“清理”清理丢失轨道的对象的“艰苦工作”。 (我不偏袒这些设计,但在其他语言中,基于独特的问题域,有时可以接受它们。)

如果您的C ++设计依赖于此类“自由代理”对象,您可以编写自己的垃圾收集器,或拥有“实用程序所有者”来清理它们。例如,MyFreeAgent构造函数可以使用MyGarbageCollector注册自己,MyFreeAgent析构函数将从MyGarbageCollector注销自己。因此MyFreeAgent实例可以被MyGarbageCollector“清除”,或者MyFreeAgent甚至可以用“delete this;”自杀,你会知道它已经消失了(因为它的析构函数会从MyGarbageCollector注销自己)。在此设计中,单个MyGarbagageCollector始终“知道”存在MyFreeAgent个实例。

这样的设计是“游泳池的深层”,即使它们有效,也应极其谨慎使用,因为它们具有大规模解构系统的巨大潜力。 (我已经使用过它们了,但是你已经 以确保问题诚实地保证了这样的设计。)

答案 1 :(得分:10)

@charley完全正确,他很好地解释了这个理论。虽然在实践中这种所有权问题可能在许多场景中发生,但最常见的一种是在继承QT类时忘记调用基类的构造函数 - 当我从头开始编码时,我总是会遇到这种情况。

采用这个非常常见的子类化 QAbstractTableModel 的例子:

from PyQt4.QtCore import *


class SomeTableModel(QAbstractTableModel):

  def __init__(self, filename):
    super(SomeTableModel, self).__init__() # If you forget this, you'll get the
                                           # "underlying C/C++ object has been 
                                           # deleted" error when you instantiate
                                           # SomeTableModel.

答案 2 :(得分:5)

currently accepted answer声称:

  

您无法测试基础对象是否存在(例如,如果它已被删除")

这是错误的。 必须是一种方法,否则PyQt不可能引发异常。以下是一些示例输出,演示了如何显式测试删除:

>>> import sip
>>> from PyQt4 import QtCore, QtGui
>>> app = QtGui.QApplication([''])
>>> w = QtGui.QWidget()
>>> w.setAttribute(QtCore.Qt.WA_DeleteOnClose)
>>> sip.isdeleted(w)
False
>>> w.close()
True
>>> sip.isdeleted(w)
True
>>> w.objectName()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: wrapped C/C++ object of type QWidget has been deleted

PyQt小部件由Python部分和C ++部分组成。如果Qt删除了C ++部分,那么将留下一个空的Python包装器对象。如果你尝试通过该状态的对象调用任何Qt方法,将会引发RuntimeError(这当然更适合于可能的段错误。)

通常,Qt不会隐式删除对象,这就是我必须在上面的示例中明确标记要删除的小部件的原因。这个一般规则的例外总是清楚地记录下来 - 例如通过指定它是Qt还是取得对象所有权的调用者。 (这就是为什么即使您不了解C ++也很难熟悉Qt文档的原因之一。)

在大多数PyQt代码中,避免问题的最简单方法是确保对象具有父对象,或者明确地保留对对象的引用(通常作为主窗口的实例属性)。

答案 3 :(得分:1)

从Python 3+开始,您可以进一步简化代码。

from PyQt4.QtCore import *

class SomeTableModel(QAbstractTableModel):
  def __init__(self, filename):
    super().__init__()   # No longer need to specify class name, or self

Super还提供了一些额外的保护措施:Understanding Python super() with __init__() methods

答案 4 :(得分:-1)

  

如何测试底层对象是否存在?

     

如何避免它?

您可以使用:

try: someObject.objectName()
except RuntimeError: return False

.objectName()测试是一个任意的询问,所有有效的QObject都应该轻易通过 如果他们失败了,他们会抛出RuntimeError,这表明他们无效 在这个粗略的例子中,我们吸收了RuntimeError,而是返回false。