【问题标题】:Understanding QTimer with Lambda and recursive function call使用 Lambda 和递归函数调用了解 QTimer
【发布时间】:2014-12-30 02:06:31
【问题描述】:

我有以下代码:

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);
}

使用连接到 lambda 函数的 timout 创建单次计时器,该函数每秒记录 lambda 函数的入口(将文本打印到标准输出)并再次调用该函数。

这没有问题。但是,如果我删除了对 deleteLater 的连接调用(在 lambda 函数下方),但在 lambda 函数中启用了 deleteLater 调用,则该函数将失败。它会打印一次,然后在尝试删除计时器对象时崩溃。

在这种情况下,两个 deleteLater 调用之间有什么区别,为什么将 deleteLater 放在 lambda 函数中会导致这里出现问题,而创建单独的连接按预期工作,即使两者都调用 deleteLater 以响应 Timer 的超时信号?

【问题讨论】:

  • deleteLater() 似乎适用于您的课程,而不是 lambda 中的计时器,或者这只是一个错字?
  • 是的,正如 @lpapp 在 lambda 中所说的 deleteLater 调用 myclass::deleteLater 而不是 QTimer::deleteLater。因此,请确保同时捕获 [this,timer] 并使用 timer->deleteLater();

标签: c++ qt c++11 lambda qtcore


【解决方案1】:

鉴于没有错字或一些我不知道的信息,我认为原因是因为您试图稍后删除您的 class 实例,而不是在堆中分配的 QTimer 实例上述方法。

如果您看一下非 lambda 版本,它会在 QTimer instance 上调用 deleteLater,因为它是连接调用中的接收器。

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

但是,在 lambda 变体中,计时器实例没有被捕获,自然地,在其当前版本中将无法访问它。为了使这两个替代方案等效,需要对代码进行此修改:

QObject::connect(timer, &QTimer::timeout, [this, timer](){
//                                               ^^^^^
    emit Log("Time out...");
    TestFunc(serverAddress, requestsFolderPath);
    timer->deleteLater();
//  ^^^^^^^
});

【讨论】:

  • 请注意,这里的问题是变量的捕获和我的一个简单的疏忽。谢谢。
【解决方案2】:

默认方法应该是不进行手动内存管理。 Qt 可以自动管理定时器的生命周期。代码在 Qt 5.4 及更高版本变得相当简单,可以是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();
   });
}

【讨论】:

    猜你喜欢
    • 2016-08-20
    • 2016-01-03
    • 1970-01-01
    • 2022-11-30
    • 2010-11-07
    • 2019-01-19
    • 1970-01-01
    相关资源
    最近更新 更多