23

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?

Rizo
  • 3,003
  • 5
  • 34
  • 49

5 Answers5

15

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
  • 5,913
  • 1
  • 33
  • 58
  • 2
    Can someone translate this into Python? – eric Aug 23 '14 at 01:03
  • See the [answer by ekhumoro](https://stackoverflow.com/a/40053635), below, which provides a concise explanation and very useful example. – djvg Jan 02 '18 at 12:20
10

@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.
Claudio
  • 2,191
  • 24
  • 49
  • This has little to do with the original question, it is more like a free association with a word in the accepted answer, to a problem that few people would have if they have much experience with PySide/PyQt programming. The problem in the OP, the "object has been deleted" error would not come up, in any obvious route, from failing to call `init`. – eric Aug 23 '14 at 01:07
  • 5
    The point is not this doesn't happen with people who have "much experience with PySide/PyQt", it is just that it DOES happen a lot, for just being careless or not having much experience anyway. And this is all this site is about, helping people solving problems. I'm pretty sure it did help more people than your empty comments and downvote. – Claudio Aug 23 '14 at 21:28
  • 2
    In fact, all his comment serves to do is irritate everybody who searches their way to this question for years to come... – Glenn Maynard Jul 10 '19 at 02:08
10

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

musicamante
  • 41,230
  • 6
  • 33
  • 58
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • It seems that saving reference to variable not prevent object from deletion. For example, I have main window and dialog. In main window I have been created some widget and save it in window attribute. In dialog constructor I\`m getting widget from main window and put it to the dialog layout. If I close dialog and open it again I will see object deleted error. Why dialog destructor deletes it\`s widget despite it has reference in another variable? – Artem Jan 11 '18 at 16:00
  • Also wanted to add that I get this issue only on Windows, but not on Linux. Full code of my test example: https://pastebin.com/gS7hEXmt – Artem Jan 11 '18 at 16:37
  • @Artem. Note that I said: "In most PyQt code". Your example is very artificial. When Qt deletes a widget, it will automatically delete all its children as well. Also, when a widget is added to a layout, it will be automatically reparented to the widget that contains the layout. So the line-edit will become a child of the dialog, and when the dialog is deleted, Qt will delete the C++ parts. leaving the python wrapper behind. If you open the dialog with `show()`, this won't happen, because the dialog won't be deleted (and its parent will keep it alive). – ekhumoro Jan 11 '18 at 18:53
  • @Artem. PS: this has got nothing to do with windows vs linux. The same behaviour will happen on both platforms. If you're seeing different behaviour, you're either running different example code, or different versions of pyqt. – ekhumoro Jan 11 '18 at 18:56
  • The code is the same, but I have different versions on my computer - on Linux: python 3.4.3, qt/pyqt 5.2.1 and on Windows: python 3.5.4, qt 5.9.2, pyqt 5.9.1. – Artem Jan 12 '18 at 12:15
  • @Artem. The pyqt-5.2.1 behaviour is buggy. The correct behaviour is to raise a `RuntimeError`, which is what the curent versions of both pyqt4 and pyqt5 do (on all platforms). – ekhumoro Jan 12 '18 at 16:57
2

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

Community
  • 1
  • 1
  • Although is doesn't answer my question its an interesting Py3 improvement. Thanks. – Rizo Apr 22 '13 at 22:59
  • 1
    this answer has nothing to do with the question – Rafał Łużyński Jan 08 '14 at 10:37
  • It was meant to be entered as a comment, not sure why it's an answer. – Christopher Sean Forgeron Jan 28 '14 at 21:08
  • 3
    Ah, now I remember why it's an answer - SO won't let me post comments for some answers directly as my rep is under 50. I just ran into that problem again with another question, so another 'comment' is posted as an answer. I seem to be able comment on my posts, but that's it. Thanks for the downvote, lets keep my rep low so I can't contribute direct comments. :-) – Christopher Sean Forgeron Jan 31 '14 at 23:20
  • It is a comment on an answer that also had nothing to do with the question. :) – eric Aug 23 '14 at 01:08
-2

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.

Community
  • 1
  • 1
dave
  • 572
  • 7
  • 16