Про QObject::deleteLater()
От: m2user  
Дата: 08.10.25 11:24
Оценка: 1 (1)
Собственно вопрос, в каких случаях следует применять deleteLater(), а в каких применим delete.
А также что делать с объектами, созданными на стеке — когда их можно использовать в слотах/сигналах.

В документации по QObject написано:
(https://doc.qt.io/qt-6/qobject.html#dtor.QObject)

QObject::~QObject()
Destroys the object, deleting all its child objects.
All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue.
However, it is often safer to use deleteLater() rather than deleting a QObject subclass directly.


Warning: All child objects are deleted. If any of these objects are on the stack or global, sooner or later your program will crash. We do not recommend holding pointers to child objects from outside the parent.
If you still do, the destroyed() signal gives you an opportunity to detect when an object is destroyed.


Warning: Deleting a QObject while pending events are waiting to be delivered can cause a crash. You must not delete the QObject directly if it exists in a different thread than the one currently executing.
Use deleteLater() instead, which will cause the event loop to delete the object after all pending events have been delivered to it.


Фраза "exists in a different thread" по-видимому означает, что объект используется из другого потока.
Т.е. это не про thread affinity, где используется термин "lives in" (A QObject instance is said to have a thread affinity, or that it lives in a certain thread).

Как я понимаю, проблема будет в случае, если в момент вызова delete уже выполняется один из подключенных слотов/сигналов?
Re: Про QObject::deleteLater()
От: Qt-Coder  
Дата: 09.10.25 04:20
Оценка: 6 (2) +1
Здравствуйте, m2user, Вы писали:

delete — это стандартный оператор C++. Его можно безопасно использовать, только если вы абсолютно уверены, что объект в данный момент не используется и не находится в середине обработки события.

deleteLater() — это метод QObject, который помечает объект для удаления. Удаление произойдет, когда управление вернется в цикл событий (event loop). Это безопаснее, потому что к моменту удаления все текущие события для этого объекта будут обработаны.

Если объект "живет" в потоке A, а вы находитесь в потоке B, вы не можете удалить его с помощью delete. Это почти гарантированно приведет к крешу, потому что поток A может в этот момент обрабатывать событие для этого объекта.

deleteLater() поставит задачу на удаление в цикл событий того потока, в котором "живет" объект, что является потокобезопасным.
Re: Про QObject::deleteLater()
От: Igore Россия  
Дата: 09.10.25 07:08
Оценка:
Здравствуйте, m2user, Вы писали:

M>Собственно вопрос, в каких случаях следует применять deleteLater(), а в каких применим delete.

100% надо использовать deleteLater
Когда ты в слоте этого объекта, просто delete не сделаешь, всё упадет, это тоже самое что сделать delete this в методе объекта.
M>А также что делать с объектами, созданными на стеке — когда их можно использовать в слотах/сигналах.
Использовать можно, вручную удалять нельзя.

M>Фраза "exists in a different thread" по-видимому означает, что объект используется из другого потока.

Он создан в другом потоке или помещен в другой поток через moveToThread.
M>Т.е. это не про thread affinity, где используется термин "lives in" (A QObject instance is said to have a thread affinity, or that it lives in a certain thread).

M>Как я понимаю, проблема будет в случае, если в момент вызова delete уже выполняется один из подключенных слотов/сигналов?

Нет, проблема будет даже если нет сигнал/слотов, там warning, можно конечно игнорировать, но это не относится к Qt, объект(работающий с ресурсами) тому потоку которому принадлежит, в том потоке и должен удаляться

З.Ы. Вот можно тему
Автор: milkpot
Дата: 29.03.24
почитать, там как раз предупреждения от Qt вида "QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread "
Re[2]: Про QObject::deleteLater()
От: m2user  
Дата: 09.10.25 09:28
Оценка:
QC>delete — это стандартный оператор C++. Его можно безопасно использовать, только если вы абсолютно уверены, что объект в данный момент не используется и не находится в середине обработки события.

Чисто технически QObject может быть реализован так, что в обработчик события передается копия (shallow copy) со счётчиком ссылок.
На мой взгляд более естественный подход, чем QObject::deleteLater.

QC>Если объект "живет" в потоке A, а вы находитесь в потоке B, вы не можете удалить его с помощью delete. Это почти гарантированно приведет к крешу, потому что поток A может в этот момент обрабатывать событие для этого объекта.

QC>deleteLater() поставит задачу на удаление в цикл событий того потока, в котором "живет" объект, что является потокобезопасным.

Т.е. из потока A вызывать delete можно? Но ведь, если объект выступает в роли sender`а (emitter`а), то он в этот момент может быть задействован из потока, в котором живет receiver.
Posted via RSDN NNTP Server 2.1 beta
Re[3]: Про QObject::deleteLater()
От: Qt-Coder  
Дата: 09.10.25 10:17
Оценка:
Здравствуйте, m2user, Вы писали:

M>Т.е. из потока A вызывать delete можно? Но ведь, если объект выступает в роли sender`а (emitter`а), то он в этот момент может быть задействован из потока, в котором живет receiver.


Это же то же самое, получатель и отправитель в разных потоках.
Re: Про QObject::deleteLater()
От: SaZ  
Дата: 10.10.25 09:34
Оценка: 2 (1)
Здравствуйте, m2user, Вы писали:

M>Собственно вопрос, в каких случаях следует применять deleteLater(), а в каких применим delete.

M>А также что делать с объектами, созданными на стеке — когда их можно использовать в слотах/сигналах.

M>...


Там уже почти на все вопросы ответили. Добавлю про объекты на стеке. Если вы объекту на стеке сделаете setParent и удалите его нового родителя, то будет как минимум двойной вызов деструктора со всеми вытекающими последствиями. А так, в целом, всё будет ок.

И если в слоте вы дёрнете sender() для объекта который помечен через deleteLater или находится в состоянии удаления, то qobject_cast<ВашТип> вернёт nullptr. Мне как-то раз понадобилось определять, удаляется ли сейчас объект или нет и я нашёл такой вот способ.

Ещё один неочевидный нюанс — это принадлежность к потокам. Если у вас объекты в общей иерархии памяти (родитель-ребёнок), то они всегда будут принадлежать одному потоку. И поменять принадлежность для всей иерахии можно только через корневой элемент. И да, делать moveToThread для объекта на стеке — тоже большой риск выстрелить себе в ногу. Надо очень хорошо будет следить за временем жизни объектов.

P.S. вспомнил что раньше в Qt был баг/фича при работе с некоторыми наследниками всяких qiodevice, например сокетами. Для таких объектов moveToThread отрабатывал лишь частично и возникали разные трудноотлаживаемые проблемы. То есть нужно такие объекты создавать сразу в том потоке где вы хотите ловить сигналы.
Отредактировано 10.10.2025 9:37 SaZ . Предыдущая версия .
Re[4]: Про QObject::deleteLater()
От: m2user  
Дата: 10.10.25 10:38
Оценка:
M>>Т.е. из потока A вызывать delete можно? Но ведь, если объект выступает в роли sender`а (emitter`а), то он в этот момент может быть задействован из потока, в котором живет receiver.

QC>Это же то же самое, получатель и отправитель в разных потоках.


Я к тому, что критерий проблемности "вызов delete из потока, отличного от того, в котором живёт объект" — неверный для объекта, который может быть emitter`ом с QtQueuedConnection.
Т.е. для таких объектов вызов delete из любого потока будет небезопасным.

Возвращаяся к deleteLater. Ты пишешь, что
> deleteLater() — это метод QObject, который помечает объект для удаления. Удаление произойдет, когда управление вернется в цикл событий (event loop). Это безопаснее, потому что к моменту удаления все текущие события для этого объекта будут обработаны.

А что насчет событий, которые сгенерированы удаляемым объектом? deleteLater ждёт пока все подписчики их обработают?
Re[5]: Про QObject::deleteLater()
От: Skorodum Россия  
Дата: 10.10.25 11:54
Оценка: +1
Здравствуйте, m2user, Вы писали:

M>Я к тому, что критерий проблемности "вызов delete из потока, отличного от того, в котором живёт объект" — неверный для объекта, который может быть emitter`ом с QtQueuedConnection.

M>Т.е. для таких объектов вызов delete из любого потока будет небезопасным.
Конечно же нет

QObject::~QObject()
All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue.


M>А что насчет событий, которые сгенерированы удаляемым объектом? deleteLater ждёт пока все подписчики их обработают?

Да, это же легко проверить:

#include <QObject>
#include <QDebug>
#include <QCoreApplication>
#include <QTimer>

class Object : public QObject
{
    Q_OBJECT
signals:
    void mySignal();
public:
    Object(QObject *parent = nullptr) : QObject(parent)
    {
        QTimer::singleShot(10, this, [this](){
            emit mySignal();
            deleteLater();
        });

        connect(this, &QObject::destroyed,  this, []{ qDebug() << "Object destroyed"; });
        connect(this, &Object::mySignal, this, []{ qDebug() << "Signal received"; });
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    const auto *obj = new Object(&app); // owned by app, but there is no double delete
    QObject::connect(obj, &QObject::destroyed, &app, &QCoreApplication::quit);
    return app.exec();
}

#include "main.moc"

Signal received
Object destroyed


Типичные случаи когда надо использовать deleteLater:
    1. Из слота самого объекта
    2. Между потоками (но обычно это признак пробленого дизайна)
    3. Асинхронные обработчики событий QNetworkReply
    4. QThread::finished -> deleteLater
Re[3]: Про QObject::deleteLater()
От: Skorodum Россия  
Дата: 10.10.25 11:59
Оценка:
Здравствуйте, m2user, Вы писали:

M>Чисто технически QObject может быть реализован так, что в обработчик события передается копия (shallow copy) со счётчиком ссылок.

Это бы был какой-то COM. Qt построен на иерархии объектов связанной с ней времени жизни объектов.

M>На мой взгляд более естественный подход, чем QObject::deleteLater.

deleteLater должен редко применяться.

M>Т.е. из потока A вызывать delete можно? Но ведь, если объект выступает в роли sender`а (emitter`а), то он в этот момент может быть задействован из потока, в котором живет receiver.

С типом подключения по умолчанию Qt::QueuedConnection — безопасно. С DirectConnection надо вызывать deleteLater.
Re[6]: Про QObject::deleteLater()
От: m2user  
Дата: 10.10.25 14:56
Оценка:
S>Конечно же нет
S>

S>QObject::~QObject()
S>All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue.


Про disconnect и очистку очереди понятно. Я говорил про те эвенты, которые уже взяты из очереди и продолжают выполняться.

M>>А что насчет событий, которые сгенерированы удаляемым объектом? deleteLater ждёт пока все подписчики их обработают?

S>Да, это же легко проверить:

В это примере emitter и receiver это один и тот же объект.
И очередь обработки событий у него одна. Т.е. обработка deleteLater всегда будет выполняться после обработки mySignal.

Меня же интересует скорее такой сценарий:

  код
#include <QObject>
#include <QDebug>
#include <QCoreApplication>
#include <QTimer>
#include <QThread>
#include <QEventLoop>

class Object : public QObject
{
  Q_OBJECT

  signals:
                 void mySignal();


 public:
  Object(QObject *parent = nullptr) : QObject(parent)
  {
  }

  void emitSignal() {
    mySignal();
  }

};

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);
  auto *obj1 = new Object();
  auto *obj2 = new Object();

  QThread thr{};
  thr.start();

  obj2->moveToThread(&thr);

  QObject::connect(obj1, &QObject::destroyed,  obj1, []{ qDebug() << "Object destroyed";  });

  QObject::connect(obj1, &Object::mySignal,  obj2, []{
    qDebug() << "Signal received";
    QThread::sleep(5);
    qDebug() << "Signal received2";
  });

  obj1->emitSignal();
  QThread::sleep(2);
  obj1->deleteLater();

  return app.exec();
}

#include "main.moc"


Signal received
Object destroyed
Signal received2


S>Типичные случаи когда надо использовать deleteLater:

S>

    S>1. Из слота самого объекта
    S>2. Между потоками (но обычно это признак пробленого дизайна)
    S>3. Асинхронные обработчики событий QNetworkReply
    S>4. QThread::finished -> deleteLater
    S>

В моем проекте в основном работа с сетью. Пункт 3.
Re[4]: Про QObject::deleteLater()
От: m2user  
Дата: 10.10.25 15:15
Оценка:
M>>Чисто технически QObject может быть реализован так, что в обработчик события передается копия (shallow copy) со счётчиком ссылок.
S>Это бы был какой-то COM. Qt построен на иерархии объектов связанной с ней времени жизни объектов.

Возможно. Но я сейчас рассматриваю вопрос времени жизни объектов именно в контексте слотов/сигналов и асинхронной работы с сетью.

S>deleteLater должен редко применяться.


Да, хотелось бы..

M>>Т.е. из потока A вызывать delete можно? Но ведь, если объект выступает в роли sender`а (emitter`а), то он в этот момент может быть задействован из потока, в котором живет receiver.

S>С типом подключения по умолчанию Qt::QueuedConnection — безопасно. С DirectConnection надо вызывать deleteLater.

Не очень понимаю эту логику. Qt::QueuedConnection и Qt::DirectConnection определяет, на каком потоке будет обработано событие (в потоке, где живет получатель, либо в потоке, вызвавшем сигнал).
Если какой-то третий поток вызвал delete (для sender`а), то чем поможет Qt::QueuedConnection?
Re[2]: Про QObject::deleteLater()
От: m2user  
Дата: 10.10.25 15:28
Оценка:
SaZ>Там уже почти на все вопросы ответили. Добавлю про объекты на стеке. Если вы объекту на стеке сделаете setParent и удалите его нового родителя, то будет как минимум двойной вызов деструктора со всеми вытекающими последствиями. А так, в целом, всё будет ок.

В иерархии parent-child я пока не углублялся, но буду иметь в виду.
В контексте слотов/сигналов получается, что объекты на стеке (локальные переменные функции или метода) вообще применять не следует, если создается connection c объектом, у которого время жизни более длительное.
И как тогда быть с RAII?

Вот тут (https://forum.qt.io/post/776397) предлагают QSharedPointer с deleteLater в качестве deleter. Насколько это годной подход?

SaZ>И если в слоте вы дёрнете sender() для объекта который помечен через deleteLater или находится в состоянии удаления, то qobject_cast<ВашТип> вернёт nullptr. Мне как-то раз понадобилось определять, удаляется ли сейчас объект или нет и я нашёл такой вот способ.


Но ведь этот способ ограничено применим: объект может быть не в состоянии удаления на момент проверки, но стать таковым после нее.

Возможно я не совсем правильно понимаю Warning из документации:

Warning: Deleting a QObject while pending events are waiting to be delivered can cause a crash. You must not delete the QObject directly if it exists in a different thread than the one currently executing. Use deleteLater() instead, which will cause the event loop to delete the object after all pending events have been delivered to it.


В ней говорится про delete объекта принимающего события.
Является ли delete объекта генерирующего события проблемным и если да, то в какой степени (cause a crash и т.д)

SaZ>Ещё один неочевидный нюанс — это принадлежность к потокам. Если у вас объекты в общей иерархии памяти (родитель-ребёнок), то они всегда будут принадлежать одному потоку. И поменять принадлежность для всей иерахии можно только через корневой элемент. И да, делать moveToThread для объекта на стеке — тоже большой риск выстрелить себе в ногу. Надо очень хорошо будет следить за временем жизни объектов.


Про thread affinity.

Из документации не совсем ясно, почему выбран подход с привязкой объекта именно к потоку выполнения.

Иными словами я могу представить, что объект не является thread safe, т.е. обращение к нему должно быть только последовательными (сериализованными). И должна быть некая очередь вызовов.
Одна на объект или группу объектов. По идее эти вызовы могут выполняться на произвольном потоке (например из некоего thread pool`а).
Re: Про QObject::deleteLater()
От: K13 http://akvis.com
Дата: 12.10.25 05:12
Оценка: 1 (1)
M>Собственно вопрос, в каких случаях следует применять deleteLater(), а в каких применим delete.
M>А также что делать с объектами, созданными на стеке — когда их можно использовать в слотах/сигналах.

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

Никаких гарантий многопоточности никто не дает, тут надо самому. Есть QPointer, но там без синхронизации ничего не гарантируется.
Re[7]: Про QObject::deleteLater()
От: Skorodum Россия  
Дата: 13.10.25 08:15
Оценка:
Здравствуйте, m2user, Вы писали:

M>Про disconnect и очистку очереди понятно. Я говорил про те эвенты, которые уже взяты из очереди и продолжают выполняться.

Очередь синхронная. Если в слоте запустить какой-то новый поток, то ответственность за ресурсы на запускающем, тут никакой магии нет.

M>Меня же интересует скорее такой сценарий:

M>...
Тут вроде нет никаких проблем если данные из obj1 не используются в слоте в другом потоке.

Про передачу данных между потоками пару лет назад подробно обсуждали.
Re[5]: Про QObject::deleteLater()
От: Skorodum Россия  
Дата: 13.10.25 08:21
Оценка: +1
Здравствуйте, m2user, Вы писали:

S>>deleteLater должен редко применяться.

M>Да, хотелось бы..
Обработка сетевых событий — валидный случай для deleteLater.

M>Если какой-то третий поток вызвал delete (для sender`а), то чем поможет Qt::QueuedConnection?

"Третий" поток не должен удалять QObject.
Re[3]: Про QObject::deleteLater()
От: SaZ  
Дата: 13.10.25 08:40
Оценка: +1
Здравствуйте, m2user, Вы писали:

SaZ>>Там уже почти на все вопросы ответили. Добавлю про объекты на стеке. Если вы объекту на стеке сделаете setParent и удалите его нового родителя, то будет как минимум двойной вызов деструктора со всеми вытекающими последствиями. А так, в целом, всё будет ок.


M>В иерархии parent-child я пока не углублялся, но буду иметь в виду.

M>В контексте слотов/сигналов получается, что объекты на стеке (локальные переменные функции или метода) вообще применять не следует, если создается connection c объектом, у которого время жизни более длительное.
M>И как тогда быть с RAII?

Это разные вещи. В контексте сигналов-слотов тут всё ок, за исключением случая, если вы кинете сигнал из объекта на стеке в другой поток, и в слоте из другого потока попытаетесь взять sender(). Аргументы будут передаваться всегда через копию, даже если сигнатура с ссылками. Там будет или nullptr или невалидный указатель. Я именно про управление памятью говорил, советую почитать: https://doc.qt.io/qt-6/objecttrees.html

M>Вот тут (https://forum.qt.io/post/776397) предлагают QSharedPointer с deleteLater в качестве deleter. Насколько это годной подход?


Ну, как правило, такое не нужно, но если осторожно использовать, то можно. Если у вас будет какой-то parent (неважно как созданный) и вы ему докинете child который лежит в таком шаредпоинтере, то при удалении парента у вас будет шаред поинтер с удалённым объектом.

В целом правило простое — если вы для управления памятью используете иерархию QObject, то либо сырые указатели на объекты в куче, либо QPointer.
Во всех остальных случаях (стек, смартпоинтеры) — никаких вызовов setParent / moveToThread.

SaZ>>И если в слоте вы дёрнете sender() для объекта который помечен через deleteLater или находится в состоянии удаления, то qobject_cast<ВашТип> вернёт nullptr. Мне как-то раз понадобилось определять, удаляется ли сейчас объект или нет и я нашёл такой вот способ.

M>Но ведь этот способ ограничено применим: объект может быть не в состоянии удаления на момент проверки, но стать таковым после нее.

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

M>Возможно я не совсем правильно понимаю Warning из документации:


M>

M>Warning: Deleting a QObject while pending events are waiting to be delivered can cause a crash. You must not delete the QObject directly if it exists in a different thread than the one currently executing. Use deleteLater() instead, which will cause the event loop to delete the object after all pending events have been delivered to it.


M>В ней говорится про delete объекта принимающего события.

M>Является ли delete объекта генерирующего события проблемным и если да, то в какой степени (cause a crash и т.д)

Советую всё-таки почитать документацию кутэ по фундаментальным вопросам. Сэкономите кучу времени потом =). Для начала: https://doc.qt.io/qt-6/threads-qobject.html
В кутэ есть понятия принадлежности объекта к потоку. Каждый QObject под капотом хранит указатель на QThread где крутится QEventLoop и будут вызываться фильтры событий и обработчики слотов (если явно не указан DirectConnection).
Но ворнинг вполне логичный — не нужно удалять то, что ещё может быть где-то вызвано. Если у вас слот вызывается в другом потоке, то в нём же, в самом конце, сделайте sender()->deleteLater(); и ваш объект корректно уничтожится в том потоке, где живёт.

SaZ>>Ещё один неочевидный нюанс — это принадлежность к потокам. Если у вас объекты в общей иерархии памяти (родитель-ребёнок), то они всегда будут принадлежать одному потоку. И поменять принадлежность для всей иерахии можно только через корневой элемент. И да, делать moveToThread для объекта на стеке — тоже большой риск выстрелить себе в ногу. Надо очень хорошо будет следить за временем жизни объектов.

M>Про thread affinity.
M>Из документации не совсем ясно, почему выбран подход с привязкой объекта именно к потоку выполнения.

Выше написал , тут в основном про то, где дёргать слоты через QueuedConnection (и подобные).

M>Иными словами я могу представить, что объект не является thread safe, т.е. обращение к нему должно быть только последовательными (сериализованными). И должна быть некая очередь вызовов.

M>Одна на объект или группу объектов. По идее эти вызовы могут выполняться на произвольном потоке (например из некоего thread pool`а).

В общем случае нет. Читайте: https://doc.qt.io/qt-6/threads-reentrancy.html
Re[5]: Про QObject::deleteLater()
От: SaZ  
Дата: 14.10.25 11:13
Оценка:
Здравствуйте, m2user, Вы писали:

M>...

M>Не очень понимаю эту логику. Qt::QueuedConnection и Qt::DirectConnection определяет, на каком потоке будет обработано событие (в потоке, где живет получатель, либо в потоке, вызвавшем сигнал).

Не совсем, Qt::QueuedConnection прекрасно работает в рамках одного потока. Просто слот будет вызван не сразу, а как только наступит следующий цикл в QEventLoop текущего потока.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.