Qt signal / slots - потреблять сигнал в одном из многих слотов?

Есть ли способ использовать будущие слоты (и остановить их выполнение) в одном из множества слотов, подключенных к одному и тому же сигналу?

Моя цель здесь - послать сигнал с сообщением для многих QObject и потреблять (останавливая итерацию для будущих слотов), когда QObject, которому принадлежит сообщение, находит его.

Насколько я понимаю из документации Qt:

Если несколько слотов подключены к одному сигналу, слоты будут выполняться один за другим в том порядке, в котором они были подключены, при передаче сигнала.

Я хочу иметь возможность остановить этот процесс из слота.

Предложения?


person CompEng88    schedule 23.07.2015    source источник


Ответы (2)


Нет, это невозможно сделать, и не стоит так думать. Отправитель должен действовать одинаково независимо от того, какое количество слотов подключено к сигналу. Это основной контракт механизма сигнальных слотов: отправитель полностью отделен от получателя и не знает о нем.

То, что вы пытаетесь сделать, - это квалифицированная отправка: есть несколько получателей, и каждый из них может обрабатывать один или несколько типов сообщений. Один из способов его реализации:

  1. Испускать (сигнализировать) QEvent. Это позволяет поддерживать развязку сигнального слота между передатчиком и приемником (ами).

  2. Затем событие может быть обработано диспетчером настраиваемых событий, который знает, какие объекты обрабатывают события данного типа.

  3. Объекты отправляют событие обычным способом и получают его в своем event() методе.

Приведенная ниже реализация позволяет объектам-получателям жить в других потоках. Вот почему ему нужно иметь возможность клонировать события.

class <QCoreApplication>
class <QEvent>

class ClonableEvent : public QEvent {
  Q_DISABLE_COPY(ClonableEvent)
public:
  ClonableEvent(int type) : QEvent(static_cast<QEvent::Type>(type)) {}
  virtual ClonableEvent * clone() const { return new ClonableEvent(type()); }
}
Q_REGISTER_METATYPE(ClonableEvent*)

class Dispatcher : public QObject {
  Q_OBJECT
  QMap<int, QSet<QObject*>> m_handlers;
public:
  Q_SLOT void dispatch(ClonableEvent * ev) {
    auto it = m_handlers.find(ev->type());
    if (it == m_handlers.end()) return;
    for (auto object : *it) {
      if (obj->thread() == QThread::currentThread())
        QCoreApplication::sendEvent(obj, ev);
      else
        QCoreApplication::postEvent(obj, ev.clone());
    }
  }
  void addMapping(QClonableEvent * ev, QObject * obj) { 
    addMapping(ev->type(), obj);
  }
  void addMapping(int type, QObject * obj) {
    QSet<QObject*> & handlers = m_handlers[type];
    auto it = handlers.find(obj);
    if (it != handlers.end()) return;
    handlers.insert(obj);
    QObject::connect(obj, &QObject::destroyed, [this, type, obj]{
      unregister(type, obj);
    });
    m_handlers[type].insert(obj);
  }
  void removeMapping(int type, QObject * obj) {
    auto it = m_handlers.find(type);
    if (it == m_handlers.end()) return;
    it->remove(obj);
  }
}

class EventDisplay : public QObject {
  bool event(QEvent * ev) {
    qDebug() << objectName() << "got event" << ev.type();
    return QObject::event(ev);
  }
public:
  EventDisplay() {}
};

class EventSource : public QObject {
  Q_OBJECT
public:
  Q_SIGNAL void indication(ClonableEvent *);
}

#define NAMED(x) x; x.setObjectName(#x)
int main(int argc, char ** argv) {
  QCoreApplication app(argc, argv);
  ClonableEvent ev1(QEvent::User + 1);
  ClonableEvent ev2(QEvent::User + 2);
  EventDisplay NAMED(dp1);
  EventDisplay NAMED(dp12);
  EventDisplay NAMED(dp2);

  Dispatcher d;
  d.addMapping(ev1, dp1);  // dp1 handles only ev1
  d.addMapping(ev1, dp12); // dp12 handles both ev1 and ev2
  d.addMapping(ev2, dp12);
  d.addMapping(ev2, dp2);  // dp2 handles only ev2

  EventSource s;
  QObject::connect(&s, &EventSource::indication, &d, &Dispatcher::dispatch);

  emit s.indication(&ev1);
  emit s.indication(&ev2);
  return 0;
}

#include "main.moc"
person Kuba hasn't forgotten Monica    schedule 23.07.2015
comment
ЗАДАЧА: этот код не является потокобезопасным перед лицом удаления объекта в других потоках. Это поправимо. - person Kuba hasn't forgotten Monica; 23.07.2015

Если соединение было в одном потоке, я думаю, вы можете выбросить исключение. Но в этом случае вы должны уловить любое исключение во время выдачи сигнала:

try {
    emit someSignal();
} catch(...) {
    qDebug() << "catched";
}

Но я считаю, что это плохая идея. Для этого я буду использовать диспетчеризацию событий.

person timocov    schedule 23.07.2015