QTimer:: singleShot и QMetaMethod:: invoke

В некоторых примерах Qt я вижу, что они используют QTimer::singleShot(0, this , SLOT(funcA())), почему бы не вызвать слот funcA напрямую? также тот же вопрос для использования QMetaMethod::invoke для вызова функции с параметрами.

Ответы

Ответ 1

Следующие строки функционально эквивалентны:

QTimer::singleShot(0, object, &Class::funcA); // Qt 5
QTimer::singleShot(0, object, SLOT(funcA())); // Qt 4
QMetaObject::invokeMethod(object, "funcA", Qt::QueuedConnection); 

Как теперь очевидно, целью является выполнение вызова в цикле событий. Вызываемый в очередь вызов приводит к отправке QMetaCallEvent в object. Это событие обрабатывается QObject::event и приводит к вызову требуемого метода. Таким образом, следующие эквиваленты эквивалентны, даже если последняя представляет собой частную деталь реализации - позволяя мне пропустить информацию о создании события:

QMetaObject::invokeMethod(object, "funcA", Qt::QueuedConnection);
QCoreApplication::postEvent(object, new QMetaCallEvent{...});

Это удобно в различных ситуациях. Например:

  • Чтобы выполнить некоторый код после того, как все ранее опубликованные события были обработаны.

  • Выполняется только после запуска цикла событий.

  • Чтобы вызвать invokable-метод, недоступный из-за модификаторов доступа С++. Вызываемые методы: сигналы, слот и объявленные методы Q_INVOKABLE.

  • Прямой вызов небезопасен (прочитайте: ошибка!), когда QObject находится в другом потоке, если вы явно не вызываете метод, документированный как потокобезопасный.

Приостановленный вызов является необходимостью, если вы хотите, чтобы цикл цикла немедленно завершался: прямой вызов quit() является no-op, если цикл еще не запущен.

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  app.quit(); // this is a no-op since the event loop isn't running yet
  return app.exec(); // will not quit as desired
}

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
  return app.exec(); // will return immediately
}

В идеале вы бы использовали postToThread из этого ответа, он предлагает самый дешевый способ вызова методов в других потоках:

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  postToThread([]{ qApp->quit(); });
}

Альтернативный способ сделать это - использовать QObject в качестве источника сигнала:

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  {
    QObject src;
    src.connect(&src, &QObject::destroyed, &app, &QCoreApplication::quit,
                Qt::QueuedConnection);
  }
  return app.exec(); // will return immediately
}

Еще один способ - использовать пользовательское событие и действовать в его деструкторе:

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  struct QuitEvent : QEvent {
    QuitEvent() : QEvent(QEvent::None) {}
    ~QuitEvent() { qApp->quit(); }
  };
  QCoreApplication::postEvent(&app, new QuitEvent);
  return app.exec(); // will return immediately
}

Ответ 2

Каждая система имеет цикл событий, в котором обрабатываются события. Скажите, как

application::processEvents()
{
// process event list..
}

Теперь место, где вы пишете QTimer::singleShot(0, this, SLOT(doSomething()));, может находиться внутри некоторого события обработки. Когда этот цикл будет выполнен, processEvents будут вызваны снова и в том, что будет выполнено doSomething().

Итак, это похоже на вызов doSomething в следующем цикле событий, а не вызов его немедленно. Надеюсь, вы получите эту идею.

Ответ 3

Эти методы также могут использоваться для вызова защищенных и закрытых членов класса (если они определены как слоты) из области, которая в противном случае требовала бы открытого доступа.