Search code examples
c++multithreadingqtc++20qt6

Use a QObject that might be deleted in another thread


I have a QObject child that is part of a hierarchy with the root root. In a concurrent application without parallelism, I can use QPointer to ensure that child still exists.

#include <QDebug>
#include <QObject>
#include <QPointer>
#include <future>

struct Child: QObject {
    using QObject::QObject;
    ~Child() { qDebug() << "delete child!"; }
};

int main() {
    auto const root = new QObject;
    auto const child = new Child(root);

    auto work = std::async(std::launch::deferred,
        [child = QPointer(child)]{
            if(child) {
                qDebug() << "child exists";
            } else {
                qDebug() << "child was deleted";
            }
        });

    delete root;

    work.get();
}
delete child!
child was deleted

This works perfectly well, since everything is executed in the same thread.

If I replace launch::deferred with launch::async, a new thread is created for execution. QPointer then does not work anymore, the output is (almost always):

delete child!
child exists

Of course I could not use QPointer for this purpose anyway, because with parallel execution the deletion could take place after the if.

What I would need is a QSharedPointer in the object hierarchy of root. Then I could give a QWeakPointer to the worker thread and lock it in the worker function, i.e. temporarily convert it into a QSharedPointer.

Is there any way to implement this approach or is there perhaps another way to use child thread-safe?


The comments have shown me that I may have asked the wrong question. What I want is to load data from the child object in the worker function if it still exists. This data is processed in the worker, which takes a few seconds. The result must be passed back to the child object. It is important that after the retrieval of the data also compellingly the set must take place again!

The QObjects are derived from QWidget in my case.

Can this be realized or do I have to separate this data part more from the GUI?

The whole thing looks something like this:

#include <QDebug>
#include <QObject>
#include <QPointer>
#include <future>

struct Root: QObject {
    using QObject::QObject;
    ~Root() {qDebug() << "delete root!"; }
};

struct Child: QObject {
    using QObject::QObject;
    ~Child() {qDebug() << "delete child!"; }
    QString getSource() { qDebug() << "get"; return data; }
    void setTarget(QString target) { qDebug() << "set" << target; }
    QString data = "data";
};

int main() {
    auto const root = new Root;
    auto const child = new Child(root);

    auto work = std::async(std::launch::async,
        [child]{
            auto data = child->getSource();
            // process data
            child->setTarget(data);
        });

    delete root;

    work.get();
}

Solution

  • To handle multithreading properly I suggest you use signals and slots. Qt already includes an excellent support for multithreading and signals and slots handle multithreading very well. This example may be useful, but there are many others: https://doc.qt.io/qt-6/qtcore-threads-queuedcustomtype-example.html.

    Refer to this to learn more: https://doc.qt.io/qt-6/threads.html.

    To stick to the example you provided, it could be simple to just store the reference to your child in a pointer somewhere and share it between the two threads, by using an appropriate QMutex. Set it in the ctor inside the mutex, reset to nullptr in the dtor inside a QMutex. Always use it under QMutex. But I do not like this approach at all.