Understanding QTimer with Lambda and recursive function call
Asked Answered
A

2

9

I have the following code:

void class::Testfunc()
{
    QTimer* timer = new QTimer;
    QObject::connect(timer, &QTimer::timeout, [this](){
        emit Log("Time out...");
        TestFunc(serverAddress, requestsFolderPath);
       // deleteLater(); //*** why does this crash if used to replace the connect below?
    });
    connect(timer, &QTimer::timeout, timer, &QTimer::deleteLater);
    timer->setSingleShot(true);
    timer->start(1000);
}

A single shot timer is created with a timout connected to a lambda function that logs the entrance to the lambda function each second (prints text to stdout) and calls the function again.

This works without issue. However, if I remove the connect call to deleteLater (below the lambda function), but enable the deleteLater call in the lambda function, the function fails. It prints once and shortly after, crashes in trying to delete the timer object.

What is the difference between the two deleteLater calls in this instance and why would placing the deleteLater in the lambda function cause a problem here, whereas creating a separate connection works as expected, even though both are calling deleteLater in response to the Timer's timeout signal?

Adulterate answered 3/11, 2014 at 11:59 Comment(2)
deleteLater() seems to be for your class, and not the timer inside the lambda, or is that just a typo?Diadem
yep, as @lpapp said deleteLater in the lambda calls myclass::deleteLater instead of QTimer::deleteLater. So make sure you capture both [this,timer] and use timer->deleteLater();Ketchan
C
12

Given that there is no typo or some information that I am not aware of, I think the reason is that because you are trying to delete your class instance later rather than the QTimer instance allocated on the heap in the aforementioned method.

If you take a look at the the non-lambda version, that calls the deleteLater on the QTimer instance as that is the receiver in the connect call.

connect(timer, &QTimer::timeout, timer, &QTimer::deleteLater);

However, in the lambda variant, the timer instance is not captured and naturally, there would be no access to it in its current version, respectively. To make the two alternatives equivalent, this modification needs to be done to the code:

QObject::connect(timer, &QTimer::timeout, [this, timer](){
//                                               ^^^^^
    emit Log("Time out...");
    TestFunc(serverAddress, requestsFolderPath);
    timer->deleteLater();
//  ^^^^^^^
});
Conformity answered 3/11, 2014 at 12:30 Comment(1)
Spot on, it's the capture of variables that's the issue here and a simple oversight on my part. Thanks.Adulterate
S
1

The default approach should be not to do manual memory management. Qt can manage timer lifetime automatically. The code becomes quite simple in Qt 5.4 and later, and can be backported for Qt 5.0-5.3:

// https://github.com/KubaO/stackoverflown/tree/master/questions/qtimer-retrofit-26713879
#include <QtCore>

struct Class : QObject {
   void TestFunc();
   void Log(const char *str) { qDebug() << str; }
};

#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
namespace compat { using QT_PREPEND_NAMESPACE(QTimer); }
#else
QT_BEGIN_NAMESPACE
Q_CORE_EXPORT void qDeleteInEventHandler(QObject *o);
QT_END_NAMESPACE
namespace compat {
using QT_PREPEND_NAMESPACE(qDeleteInEventHandler);
template <class Fun> struct SingleShotHelper : QObject, Fun {
   QBasicTimer timer;
   template <class F> SingleShotHelper(int msec, QObject *context, F &&fun) :
      QObject(context ? context : QAbstractEventDispatcher::instance()),
      Fun(std::forward<F>(fun)) {
      timer.start(msec, this);
      if (!context)
         connect(qApp, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
   }
   void timerEvent(QTimerEvent *ev) override {
      if (ev->timerId() != timer.timerId()) return;
      timer.stop();
      (*this)();
      qDeleteInEventHandler(this);
   }
};
using Q_QTimer = QT_PREPEND_NAMESPACE(QTimer);
class QTimer : public Q_QTimer {
   Q_OBJECT
public:
   QTimer(QObject *parent = {}) : Q_QTimer(parent) {} // C++17: using Q_QTimer::Q_QTimer;
   template <class Fun>
   inline static void singleShot(int msec, QObject *context, Fun &&fun) {
      new SingleShotHelper<Fun>(msec, context, std::forward<Fun>(fun));
   }
}; }
#endif

void Class::TestFunc() {
   compat::QTimer::singleShot(1000, this, [this]{
      emit Log("Timeout...");
      TestFunc();
   });
}
Spank answered 16/4, 2018 at 14:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.