开发者

Understanding the "underlying C/C++ object has been deleted" error

It's not the first time I am getting the RuntimeError: underlying C/C++ object has been deleted. I've solved it many times altering my code in a random but intuitive way but now I am facing this again and just don't understand why it happens... What I ask for is a generic approach to confront and solve this error.

I will not post code samples here because my project is too complex and I just can't figure out where's the bug. And also because I am asking for the universal solution not just for this case.

Why can the 'underlying C/C++' objects be deleted?

How to avoid it?

开发者_运维技巧 How to test if the underlying object exists?


You cannot test if the underlying object exists (e.g., if it has been "deleted"). There is some "trickery" you can employ to "kind-of" help with this, but I don't recommend it. Rather, IMHO, I'd guess your design lacks strong ownership semantics. (That may be necessary depending on the complexity of the problem and your domain, but I'd recommend against it, if it is avoidable.)

Your problem is ownership semantics. Most especially in C++, as opposed to other languages that employ memory "garbage collection", you MUST have a design where you have a strong understanding of "who owns what". Your design should make it "easy and obvious" to know what objects exist, and who "owns" them.

Generally, this means to centralize the "creation" and "deletion" of objects into a "parent" of some kind, where there is never any question as to, "Whose object is this?" and "What are your objects that you manage?"

For example, a "Parent" class will allocate member objects that it "needs", and only that parent deletes those objects. The parent's destructor ensures those members are deleted, so those objects don't accidentally get "left around". Thus, the parent "knows" its member objects, and when the parent is gone, you KNOW the member objects also are gone. If your design is strongly coupled, then the member objects may reference the parent object, but that is the "deep end of the pool" for ownership semantics (and usually not recommended).

Some designs can be legitimately very complicated. However, in general, ownership semantics should NEVER be complicated: You should ALWAYS know "who owns what", and thus, you should ALWAYS know "what objects exist".

Other designs are possible with garbage collection languages, where objects may be "free agents" and you trust the garbage collector to do the "hard work" of cleaning up objects about which you've lost track. (I'm not partial to those designs, but concede them to be sometimes acceptable in those other languages, based on unique problem domains.)

If your C++ design relies upon such "free agents" objects, you can write your own garbage collector, or own "utility owner" to clean them up. For example, the MyFreeAgent constructor could register itself with the MyGarbageCollector, and the MyFreeAgent destructor would unregister itself from the MyGarbageCollector. Thus, MyFreeAgent instances could be "cleaned up" by the MyGarbageCollector, or the MyFreeAgent could even kill itself with "delete this;", and you would KNOW that it is gone (because its destructor would unregister itself from the MyGarbageCollector). In this design, a single MyGarbagageCollector would ALWAYS "know" what MyFreeAgent instances exist.

Such designs are the "deep end of the pool" and should be used with extreme caution, even though they work, because they have the strong potential to massively de-structure your system. (I've used them, but you've got to be sure the problem honestly warrants such a design.)


@charley is completely right, he explained the theory very well. Although in practice this ownership problem can happen in many scenarios, one of the most common is forgetting to call the constructor of the base class while subclassing a QT class - from time to time I always get stuck with this when I start coding from scratch.

Take this very common example of subclassing a 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.


The currently accepted answer claims that:

You cannot test if the underlying object exists (e.g., if it has been "deleted")

This is wrong. There must be a way to do this, otherwise it would be impossible for PyQt to raise an exception. Here is some example output that demonstrates how to explicitly test for deletion:

>>> 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

A PyQt widget consists of a Python part, and a C++ part. If the C++ part is deleted by Qt, an empty Python wrapper object will be left behind. And if you attempt to call any Qt methods via an object in that state, a RuntimeError will be raised (which is of course preferrable to a likely segfault).

In general, Qt tends not to implicitly delete objects, which is why I had to explicitly mark the widget for deletion in the example above. The exceptions to this general rule are always clearly documented - for example by specifying whether it's Qt or the caller that takes ownership of an object. (This is one reason why it can really pay to become familiar with the Qt documentation, even if you have no knowledge of C++).

In most PyQt code, the simplest way to avoid problems is to either make sure the object has a parent, or explictly keep a reference to the object (often as an instance attribute of the main window).


As of Python 3+ you can simplify the code even further.

from PyQt4.QtCore import *

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

Super also grants some extra safeguards mentioned here: Understanding Python super() with __init__() methods


How to test if the underlying object exists?

How to avoid it?

You could use:

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

The .objectName() test is an arbitrary interrogation which all valid QObjects should easily pass.
If they fail, they'll throw a RuntimeError which is an indication they are invalid.
In this crude example we absorb the RuntimeError and instead return false.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜