Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QFutureWatcher Memory Management



  • Hi guys,

    This is more of a C++ question but with QT classes.

    I recently learned about the magic of using QFuture, QFutureWatcher and lambda functions to cheat the signal / slot system :)

    As an example I have this code which works perfectly. I can pass any parameters through my lambda function.

    QFutureWatcher<void>* watcher = new QFutureWatcher<void>;
    QFuture<void> future;
    
    QObject::connect(watcher,&QFutureWatcher<void>::finished,[=](){threadFinished(0,watcher);});
    
    future = QtConcurrent::run(this,&MainWindow::threadedFunction, 0);
    
    watcher->setFuture(future);
    
    void MainWindow::threadedFunction(int num)
    {
        qDebug() << "RUNNING:[" << num << "]\n";
        return;
    }
    
    void MainWindow::threadFinished(int num, QFutureWatcher<void>* watcher)
    {
        qDebug() << "FINISHED:[" << num << "]\n";
        qDebug() << watcher;
    }
    

    Looking at this thread there is no other way to make this work other than initializing a QFutureWatcher pointer since if don't use the 'new' keyword, the object goes out of scope and gets deleted before it fires its 'finished' signal:

    https://www.qtcentre.org/threads/21670-QFutureWatcher-finished()-signal-not-working

    This means I have to either include the QFutureWatcher as part of my class definition or pass it through the lambda function to my 'threadFinished' function and delete it there.

    I wanted to confirm that there is no way around this and I have to use the 'new' keyword every time? From the documentation example it seems that you shouldn't have to do this:

    https://doc.qt.io/qt-5/qfuturewatcher.html

    Thanks a lot


  • Lifetime Qt Champion

    hi
    The examples are showing the usage of the class
    and are not so concerned about scope issue.

    So the normal solution with asynchronous API/classes like
    QNetworkManager / QFutureWatcher and is to either new it, or have as part of a class so it will outlive the function call.



  • @mrjj Got it. Since QFutureWatcher inherits QObject, it also sounds like connecting 'finished' to 'deleteLater' is a solution but from what I just ran it keeps sitting there like its paying rent. Do you have any thoughts on this?

    I think I'll just pass the pointer to my result handler and delete it there if nothing else works out.


  • Lifetime Qt Champion

    Hi
    Hmm. That should actually have worked.
    ( 'finished' -> 'deleteLater)
    Capturing the pointer and handled it there should be fine
    but deletelater would been slightly more elegant. :)


  • Moderators

    Connecting finished to deleteLater should work. Maybe the task is not actually finishing?

    Passing watcher into the handler unnecessarily complicates the function IMO and kinda mixes responsibilities. You could just delete it in the lambda instead.
    Speaking of lambdas, it's a good idea to give the connection the third parameter - the object ( I guess this in the example code). Otherwise you're running a risk of this being deleted before the watcher and then the lambda would crash trying to call threadFinished() on a deleted object.



  • @mrjj @Chris-Kawa I just ran it again, it does work. I was checking if it was set to NULL right when I received 'destroyed' which is sent right before deletion... I was too impatient...

    Also deleting in lambda works as you said and makes more sense. Either way I think both are clean ways of doing this.

    I'll pass 'this' to it also, its just that I'd seen people do it without passing the object.

    Either way this would be the updated connection:

    QObject::connect(watcher,&QFutureWatcher<void>::finished,this,[=](){this->threadFinished(0,watcher);delete watcher;});
    

    And this confirms deletion:

    QObject::connect(watcher,&QFutureWatcher<void>::destroyed,this,[=](){qDebug() << "DELETED WATCHER";});
    

    Thanks for the quick responses guys!


Log in to reply