Понятно, что ошибка "базового объекта C/С++ была удалена"

Это не первый раз, когда я получаю RuntimeError: underlying C/C++ object has been deleted. Я решил это много раз, изменяя мой код случайным, но интуитивным способом, но теперь я снова сталкиваюсь с этим и просто не понимаю, почему это происходит... То, о чем я прошу, является общим подходом к решению этой проблемы.

Я не буду отправлять образцы кода здесь, потому что мой проект слишком сложный, и я просто не могу понять, где ошибка. А также потому, что я прошу универсального решения не только для этого случая.

Почему объекты "базового C/С++" могут быть удалены?
Как этого избежать? Как проверить, существует ли базовый объект?

Ответ 1

Вы не можете проверить, существует ли базовый объект (например, если он был удален). Существует некоторая "хитрость", которую вы можете использовать для "доброй" помощи, но я не рекомендую ее. Скорее, IMHO, я бы предположил, что у вашего дизайна не хватает сильной семантики владения. (Это может потребоваться в зависимости от сложности проблемы и вашего домена, но я бы рекомендовал против нее, если ее можно избежать.)

Ваша проблема - это семантика собственности. В особенности в С++, в отличие от других языков, на которых используется "сбор мусора" памяти, вы ДОЛЖНЫ иметь дизайн, в котором у вас есть сильное понимание того, "кому принадлежит". Ваш дизайн должен сделать его "легким и очевидным", чтобы знать, какие существуют объекты, и кто "владеет" ими.

Как правило, это означает централизовать "создание" и "удаление" объектов в "родительский" какой-либо тип, где никогда не возникает вопросов, "Чей это объект?". и "Каковы ваши объекты, которыми вы управляете?"

Например, класс "Родитель" будет выделять объекты-члены, которые ему "нужны", и только этот родитель удаляет эти объекты. Родительский деструктор гарантирует, что эти члены будут удалены, поэтому эти объекты случайно не "останутся". Таким образом, родительский "знает" его объекты-члены, а когда родитель ушел, вы ЗНАЕТ, что объекты-члены также исчезли. Если ваш проект сильно связан, то объекты-члены могут ссылаться на родительский объект, но это "глубокий конец пула" для семантики собственности (и обычно не рекомендуется).

Некоторые проекты могут быть законно очень сложными. Тем не менее, в целом, семантика собственности НИКОГДА не должна быть сложной: вы должны ВСЕГДА знать "кто владеет тем", и, таким образом, вы должны ВСЕГДА знать "какие существуют объекты".

Другие варианты дизайна возможны с помощью языков сбора мусора, где объекты могут быть "свободными агентами", и вы доверяете сборщику мусора, чтобы выполнить "тяжелую работу" по очистке объектов, по которым вы потеряли трек. (Я не являюсь частью этих проектов, но признаю их приемлемыми на тех других языках, основанных на уникальных проблемных областях.)

Если ваш дизайн на С++ основан на таких объектах "свободных агентов", вы можете написать свой собственный сборщик мусора или собственный "владелец утилиты", чтобы очистить их. Например, конструктор MyFreeAgent мог бы зарегистрировать себя с помощью MyGarbageCollector, а деструктор MyFreeAgent отменил бы себя от MyGarbageCollector. Таким образом, экземпляры MyFreeAgent могут быть "очищены" с помощью MyGarbageCollector, или MyFreeAgent может даже убить себя "delete this;", и вы ЗНАЛИ, что он ушел (потому что его деструктор отменил бы себя от MyGarbageCollector). В этом дизайне один MyGarbagageCollector ВСЕГДА "знал", что существуют экземпляры MyFreeAgent.

Такие конструкции - это "глубокий конец пула", и их следует использовать с особой осторожностью, даже несмотря на то, что они работают, потому что они обладают сильным потенциалом для серьезной деструкции вашей системы. (Я использовал их, но вы должны быть уверены, что проблема честно гарантирует такой дизайн.)

Ответ 2

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

Ответ 3

принятый в настоящее время ответ утверждает, что:

Вы не можете проверить, существует ли базовый объект (например, если он был удален)

Это неправильно. Там должен быть способ сделать это, иначе 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 и части С++. Если часть С++ удаляется Qt, пустой объект оболочки Python будет оставлен позади. И если вы попытаетесь вызвать любые методы Qt через объект в этом состоянии, будет добавлен RuntimeError (что, конечно, предпочтительнее вероятного segfault).

В общем случае Qt стремится не косвенно удалять объекты, поэтому я должен был явно пометить виджет для удаления в приведенном выше примере. Исключения из этого общего правила всегда четко задокументированы - например, путем указания того, принадлежит ли Qt или вызывающему лицу права собственности на объект. (Это одна из причин, почему он действительно может заплатить за знакомство с Qt-документацией, даже если у вас нет знаний о С++).

В большинстве кодов PyQt самым простым способом избежать проблем является либо удостовериться, что объект имеет родительский объект, либо явно содержит ссылку на объект (часто как атрибут экземпляра главного окна).

Ответ 4

Как и в случае с 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 также предоставляет некоторые дополнительные гарантии, упомянутые здесь: Понимание Python super() с методами __init __()

Ответ 5

Как проверить, существует ли базовый объект?

Как его избежать?

Вы можете использовать:

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

Тест .objectName() - это произвольный опрос, который все допустимые QObjects должны легко пройти.
Если они потерпят неудачу, они будут бросать RuntimeError, что является признаком того, что они недействительны.
В этом грубом примере мы поглощаем RuntimeError и вместо этого возвращаем false.