Как delete и deleteLater работает в отношении сигналов и слотов в Qt?

Есть объект класса QNetworkReply. Есть слот (в каком-то другом объекте), связанный с его сигналом finished (). Сигналы синхронные (по умолчанию). Есть только одна ветка.

В какой-то момент я хочу избавиться от обоих предметов. Больше никаких сигналов или чего-либо от них. Я хочу, чтобы они ушли. Ну подумал я воспользуюсь

delete obj1; delete obj2;

Но могу ли я на самом деле? Спецификации для ~ QObject говорят:

Удаление QObject во время ожидания доставки ожидающих событий может вызвать сбой.

Что такое «ожидающие события»? Может ли это означать, что пока я звоню своему delete, уже есть некоторые «ожидающие события», которые нужно доставить, и что они могут вызвать сбой, и я не могу проверить, есть ли они?

Допустим, я звоню:

obj1->deleteLater(); obj2->deleteLater();

Чтобы быть в безопасности.

Но действительно ли я в безопасности? deleteLater добавляет событие, которое будет обработано в основном цикле, когда туда попадет управление. Могут ли уже быть какие-то ожидающие события (сигналы) для obj1 или obj2, ожидающие обработки в основном цикле до обработки deleteLater? Это было бы очень прискорбно. Я не хочу писать код, проверяющий статус «несколько удален» и игнорирующий входящий сигнал во всех моих слотах.


person stach    schedule 03.02.2011    source источник
comment
Похоже, obj->disconnect(); obj->deleteLater(); - правильный путь:   -  person stach    schedule 16.03.2011
comment
После чтения источника QObject кажется, что deleteLater() просто отправляет QDeferredDeleteEvent объекту, для которого был вызван deleteLater(). Когда это событие получено QObject, его обработчик событий в конечном итоге вызовет обычный delete, который, в свою очередь, вызовет деструктор QObject. Отключение сигнала не происходит до конца деструктора, поэтому я предполагаю, что QObject будет запускать слоты, которые вызываются сигналами DirectConnection, которые излучаются после вызова deleteLater(), но до того, как цикл событий вернется.   -  person Kasheen    schedule 04.04.2018


Ответы (4)


Удаление QObjects обычно безопасно (т.е. в обычной практике; могут быть патологические случаи, о которых я не знаю), если вы будете следовать двум основным правилам:

  • Никогда не удаляйте объект в слоте или методе, который вызывается прямо или косвенно (синхронный, тип соединения «прямой») сигнал от удаляемого объекта. Например. если у вас есть класс Operation с сигналом Operation :: finished () и диспетчером слотов :: operationFinished (), вы не хотите удалять объект операции, который испустил сигнал в этом слоте. Метод, испускающий сигнал finished (), может продолжить доступ к «this» после отправки (например, доступ к члену), а затем работать с недопустимым указателем «this».

  • Точно так же никогда не удаляйте объект в коде, который вызывается синхронно из обработчика событий объекта. Например. не удаляйте SomeWidget в его SomeWidget :: fooEvent () или в методах / слотах, которые вы вызываете оттуда. Система событий продолжит работу над уже удаленным объектом -> Сбой.

И то, и другое может быть сложно отследить, поскольку обратные трассировки обычно выглядят странно (например, сбой при доступе к переменной-члену POD), особенно когда у вас есть сложные цепочки сигналов / слотов, где удаление может происходить на несколько шагов вниз, первоначально инициированное сигналом или событием из объект, который удаляется.

Такие случаи являются наиболее частым вариантом использования deleteLater (). Он гарантирует, что текущее событие может быть завершено до того, как элемент управления вернется в цикл событий, который затем удаляет объект. Другой, который я часто нахожу лучшим способом, - это отложить все действие, используя соединение в очереди / QMetaObject :: invokeMethod (..., Qt :: QueuedConnection).

person Frank Osterfeld    schedule 03.02.2011
comment
Одним из примеров этого сбоя может быть: из события focusOut некоторого виджета я удаляю несколько дочерних виджетов. Отключение фокуса запускается щелчком по одному из виджетов, которые нужно удалить. В этом примере удаление небезопасно, потому что при достижении цикла событий объект уже исчез и вызывает сбой, когда он пытается доставить событие щелчка на этот виджет. deleteLater безопасен, потому что объект помечен для удаления, и цикл событий знает, что это событие не должно быть доставлено, потому что объект уже был удален - person AKludges; 25.04.2018

В следующих двух строках упомянутой вами документации содержится ответ.

Из ~ QObject,

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

Он специально говорит нам не удалять из других тем. Поскольку у вас однопоточное приложение, можно безопасно удалить QObject.

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

person liaK    schedule 04.02.2011
comment
А как насчет моего второго сценария? Могут ли слоты в объекте по-прежнему вызываться после того, как я вызываю для него deleteLater? - person stach; 04.02.2011

Вы можете найти ответ на свой вопрос, прочитав об одном из правил для дельта-объектов в котором говорится об этом:

Signal Safe (SS).
Должен быть безопасным вызов методов объекта, включая деструктор, из слота, вызываемого одним из его сигналов.

Фрагмент:

По своей сути QObject поддерживает удаление во время сигнализации. Чтобы воспользоваться этим преимуществом, вам просто нужно быть уверенным, что ваш объект не пытается получить доступ ни к одному из своих членов после удаления. Однако большинство объектов Qt не написаны таким образом, и для них нет никаких требований. По этой причине рекомендуется всегда вызывать deleteLater (), если вам нужно удалить объект во время одного из его сигналов, потому что есть вероятность, что «delete» просто приведет к сбою приложения.

К сожалению, не всегда ясно, когда следует использовать «delete» или «deleteLater ()». То есть не всегда очевидно, что у кодового тракта есть источник сигнала. Часто у вас может быть блок кода, который использует «удаление» для некоторых объектов, которые сегодня безопасны, но в какой-то момент в будущем этот же блок кода будет вызван из источника сигнала, и теперь ваше приложение внезапно дает сбой. Единственное общее решение этой проблемы - постоянно использовать deleteLater (), даже если на первый взгляд это кажется ненужным.

Обычно я считаю Правила дельта-объектов обязательными к прочтению для каждого разработчика Qt. Это отличный материал для чтения.

person Piotr Dobrogost    schedule 03.02.2011
comment
Если вы перейдете по ссылке на DOR, вы должны будете перейти по ссылкам на этой странице для дальнейшего чтения, например перейдите по ссылке "Signal Safe". Первую страницу по ссылке сложно понять без контекста. (Я преследую сбой при выходе с помощью PyQt в Windows, мое приложение даже не удаляет какие-либо объекты, но я надеюсь, что ссылка на DOR предложит понимание.) - person bootchk; 04.03.2014

Насколько мне известно, это в основном проблема, если объекты существуют в разных потоках. Или, может быть, когда вы на самом деле обрабатываете сигналы.

В противном случае удаление QObject сначала отключит все сигналы и слоты и удалит все ожидающие события. Так же, как вызов Disconnect ().

person thorsten müller    schedule 03.02.2011