Як працює delete і deleteLater щодо сигналів і слотів у Qt?


78

Є об’єкт класу QNetworkReply. Існує слот (в якомусь іншому об’єкті), підключений до його сигналу готової (). Сигнали синхронні (типові). Існує лише одна нитка.

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

delete obj1; delete obj2;

Але чи можу я насправді? Специфікації для ~ QObject говорять:

Видалення об'єкта QOb, поки очікують на доставку очікувані події, може призвести до аварійного завершення роботи.

Що таке "очікувані події"? Чи може це означати, що поки я телефоную до свого delete, вже є деякі "очікувані події", які мають бути доставлені, і що вони можуть спричинити збій, і я не можу перевірити, чи є такі?

Тож, скажімо, я телефоную:

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

Щоб бути в безпеці.

Але, чи справді я в безпеці? deleteLaterДодає подія , яке буде оброблятися в головному циклі , коли елемент управління отримує там. Чи можуть бути деякі очікувані події (сигнали) для obj1або obj2вже там, які чекають обробки в основному циклі до обробки deleteLater? Це було б дуже прикро. Я не хочу писати код, перевіряючи статус "дещо видаленого" та ігноруючи вхідний сигнал у всіх моїх слотах.


4
Схоже, obj->disconnect(); obj->deleteLater();це правильний шлях:
stach

1
Прочитавши джерело QObject, здається, що deleteLater()просто відправляє a QDeferredDeleteEventдо об’єкта, на який deleteLater()було викликано. Коли цю подію отримує QObject, її обробник подій в кінцевому підсумку викличе регулярний, deleteякий, у свою чергу, викликає деструктор QObject. Відключення сигналу не відбувається до кінця деструктора, тому я думаю, що QObject буде запускати слоти, які викликаються сигналами DirectConnection, що видаються після виклику, deleteLater()але до повернення циклу подій.
Kasheen,

Відповіді:


74

Видалення об'єктів QObjects, як правило, безпечно (тобто на звичайній практиці; можуть бути патологічні випадки, про які я не знаю банкомата), якщо дотримуватись двох основних правил:

  • Ніколи не видаляйте об'єкт у слоті або методі, який викликається прямо чи опосередковано сигналом (синхронним, тип з'єднання "прямий") з об'єкта, який потрібно видалити. Наприклад, якщо у вас є клас Operation з сигналом Operation :: finished () та менеджером слотів :: operationFinished (), ви не хочете видаляти об'єкт операції, який випромінював сигнал у цьому слоті. Метод, що випромінює готовий () сигнал, може продовжувати отримувати доступ до "this" після випромінювання (наприклад, доступ до члена), а потім працювати на недійсному покажчику "this".

  • Так само ніколи не видаляйте об'єкт у коді, який викликається синхронно з обробника подій об'єкта. Наприклад, не видаляйте SomeWidget у його SomeWidget :: fooEvent () або в методах / слотах, які ви викликаєте звідти. Система подій буде продовжувати працювати на вже видаленому об’єкті -> Збій.

І те, і інше може бути складно відстежити, оскільки зворотне відстеження зазвичай виглядає дивним (як збій під час доступу до змінної члена POD), особливо коли у вас складні ланцюжки сигналів / слотів, де видалення може відбутися на декількох етапах, спочатку ініційованих сигналом або подією від об’єкт, який видаляється.

Такі випадки є найпоширенішим варіантом використання deleteLater (). Він гарантує, що поточну подію можна завершити до того, як елемент керування повернеться до циклу подій, який потім видалить об’єкт. Інший, я часто вважаю кращим способом відкласти всю дію, використовуючи підключення в черзі / QMetaObject :: invokeMethod (..., Qt :: QueuedConnection).


1
Одним з прикладів цього збою може бути: із події focusOut деякого віджета я видаляю деякі дочірні віджети. Фокус викликається натисканням одного з віджетів, які потрібно видалити. У цьому прикладі видалення не є безпечним, оскільки при досягненні циклу подій об’єкт уже зник і викликає збій, коли він намагається доставити клік-подію до цього віджету. deleteLater безпечний, оскільки об’єкт позначений для видалення, і цикл подій знає, що цю подію не слід доставляти, оскільки об’єкт вже видалено
AKludges

22

Наступні два рядки згаданих документів говорять про відповідь.

З ~ QObject ,

Видалення об'єкта QOb, поки очікують на доставку очікувані події, може призвести до аварійного завершення роботи. Ви не повинні видаляти QObject безпосередньо, якщо він існує у потоці, відмінному від поточного, що виконується. Замість цього використовуйте deleteLater (), що призведе до того, що цикл подій видалить об’єкт після того, як йому будуть доставлені всі очікувані події.

Це конкретно говорить нам, що не слід видаляти з інших тем. Оскільки у вас є одна різьбова програма, її можна безпечно видалити QObject.

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


6
А як щодо мого другого сценарію? Чи можна все ще викликати слоти в об'єкті після того, як я викликаю на ньому deleteLater?
стех

15

Ви можете знайти відповідь на своє запитання, прочитавши про одне з правил об'єкта Delta, яке говорить:

Signal Safe (SS).
Потрібно безпечно викликати методи на об’єкті, включаючи деструктор, зсередини слота, що викликається одним із його сигналів.

Фрагмент:

По суті, QObject підтримує видалення під час сигналізації. Для того, щоб скористатися нею, вам просто потрібно бути впевненим, що ваш об'єкт не намагається отримати доступ до будь-якого власного учасника після видалення. Однак більшість об'єктів Qt написані не таким чином, і для них також немає вимоги. З цієї причини рекомендується завжди викликати deleteLater (), якщо вам потрібно видалити об'єкт під час одного з його сигналів, оскільки шанси на те, що 'delete' просто призведе до збою програми.

На жаль, не завжди зрозуміло, коли слід використовувати 'delete' проти deleteLater (). Тобто не завжди очевидно, що шлях коду має джерело сигналу. Часто у вас може бути блок коду, який використовує "видалити" для деяких об'єктів, що є безпечним сьогодні, але в якийсь момент у майбутньому цей самий блок коду в кінцевому підсумку отримує виклик із джерела сигналу, і тепер раптом ваш додаток аварійно завершує роботу. Єдиним загальним рішенням цієї проблеми є постійне використання deleteLater (), навіть якщо на перший погляд це здається непотрібним.

Як правило, я вважаю Правила Delta Object обов'язковим прочитанням для кожного розробника Qt. Це чудовий матеріал для читання.


1
Якщо ви переходите за посиланням на DOR, ви повинні перейти за посиланнями на цій сторінці для подальшого читання, наприклад, перейдіть за посиланням на "Signal Safe". Першу сторінку з посилання важко зрозуміти без контексту. (Я переслідую збій на виході за допомогою PyQt в Windows, мій додаток навіть не видаляє жодних об’єктів, але я сподіваюся, що посилання на DOR запропонує інформацію.)
bootchk 03.03.14

4

Наскільки мені відомо, це головним чином проблема, якщо об’єкти існують у різних потоках. Або, можливо, поки ви фактично обробляєте сигнали.

В іншому випадку видалення об'єкта QO спочатку від'єднає всі сигнали та слоти та видалить усі очікувані події. Як заклик до відключення ().

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.