Event Bus Pattern Overview
The Event Bus pattern provides a publish-subscribe mechenism that enables loose coupling between components. Rather than maintaining direct references to subscribers, publishers dispatch events through a central mediator, and interested components register their interest at runtime. This architectural approach proves particularly valuable in large applications where components may be developed independently or exist in different modules.
Custom event types require metatype registration in the application entry point
qRegisterMetaType<InputEvent>("InputEvent");
Event Dispatcher Header
#ifndef EVENTDISPATCHER_H
#define EVENTDISPATCHER_H
#include <functional>
#include <type_traits>
#include <QObject>
#include <QHash>
#include <QVector>
#include <QReadWriteLock>
#include <QDebug>
#include <QSharedPointer>
#include <QThread>
class EventDispatcher : public QObject
{
Q_OBJECT
public:
explicit EventDispatcher();
~EventDispatcher() override;
template <typename EventType>
void registerHandler(QObject* receiver, void (QObject::*callback)(const EventType&))
{
if (!receiver || !callback) {
return;
}
QWriteWriteLock locker(&m_accessLock);
const QByteArray eventKey = typeid(EventType).name();
auto wrappedCallback = [receiver, callback](const EventType& payload) {
(receiver->*callback)(payload);
};
auto baseEntry = new EventHandler<EventType>(receiver, eventKey, wrappedCallback);
m_eventRegistry[eventKey].push_back(baseEntry);
m_subscriberIndex[receiver].push_back(baseEntry);
connect(receiver, &QObject::destroyed,
this, &EventDispatcher::handleReceiverDestruction);
}
template <typename EventType>
void unregisterHandler(QObject* receiver)
{
QWriteWriteLock locker(&m_accessLock);
const QByteArray eventKey = typeid(EventType).name();
if (!m_subscriberIndex.contains(receiver)) {
return;
}
auto& subscriberHandlers = m_subscriberIndex[receiver];
QVector<HandlerBase*> matchingHandlers;
for (auto handler : subscriberHandlers) {
if (handler->eventType == eventKey) {
matchingHandlers.push_back(handler);
}
}
for (auto handler : matchingHandlers) {
subscriberHandlers.removeAll(handler);
m_eventRegistry[eventKey].removeAll(handler);
delete handler;
}
if (subscriberHandlers.isEmpty()) {
m_subscriberIndex.remove(receiver);
disconnect(receiver, &QObject::destroyed,
this, &EventDispatcher::handleReceiverDestruction);
}
}
template <typename EventType>
void broadcast(const EventType& eventData)
{
QReadWriteLock locker(&m_accessLock);
const QByteArray eventKey = typeid(EventType).name();
if (!m_eventRegistry.contains(eventKey) || m_eventRegistry[eventKey].isEmpty()) {
return;
}
auto eventHolder = QSharedPointer<EventType>::create(eventData);
auto registeredHandlers = m_eventRegistry[eventKey];
locker.unlock();
for (auto baseHandler : registeredHandlers) {
auto typedHandler = dynamic_cast<EventHandler<EventType>*>(baseHandler);
if (typedHandler) {
QObject* target = typedHandler->subscriber;
auto executionContext = [typedHandler, eventHolder]() {
typedHandler->callback(*eventHolder);
};
if (target->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(target, executionContext,
Qt::QueuedConnection);
} else {
executionContext();
}
}
}
}
template <typename EventType>
bool hasSubscribers() const
{
QReadWriteLock locker(&m_accessLock);
const QByteArray eventKey = typeid(EventType).name();
return m_eventRegistry.contains(eventKey) &&
!m_eventRegistry[eventKey].isEmpty();
}
signals:
private slots:
void handleReceiverDestruction(QObject* object);
private:
void purgeSubscriber(QObject* object);
private:
struct HandlerBase {
QObject* subscriber;
QByteArray eventType;
virtual ~HandlerBase() = default;
HandlerBase(QObject* obj, const QByteArray& type)
: subscriber(obj), eventType(type) {}
};
template <typename T>
struct EventHandler : public HandlerBase {
std::function<void(const T&)> callback;
EventHandler(QObject* obj, const QByteArray& type,
const std::function<void(const T&)>& fn)
: HandlerBase(obj, type), callback(fn) {}
};
private:
QHash<QByteArray, QVector<HandlerBase*>> m_eventRegistry;
QHash<QObject*, QVector<HandlerBase*>> m_subscriberIndex;
mutable QReadWriteLock m_accessLock;
};
#endif // EVENTDISPATCHER_H
Event Dispatcher Implementation
#include "EventDispatcher.h"
EventDispatcher::EventDispatcher() : QObject() { }
EventDispatcher::~EventDispatcher()
{
QWriteWriteLock locker(&m_accessLock);
for (auto subscriber : m_subscriberIndex.keys()) {
disconnect(subscriber, &QObject::destroyed,
this, &EventDispatcher::handleReceiverDestruction);
}
for (auto handlerCollection : m_eventRegistry.values()) {
for (auto handler : handlerCollection) {
delete handler;
}
}
m_eventRegistry.clear();
m_subscriberIndex.clear();
}
void EventDispatcher::handleReceiverDestruction(QObject* object)
{
purgeSubscriber(object);
}
void EventDispatcher::purgeSubscriber(QObject* object)
{
QWriteWriteLock locker(&m_accessLock);
if (!m_subscriberIndex.contains(object)) {
return;
}
auto handlers = m_subscriberIndex[object];
for (auto handler : handlers) {
auto& eventHandlers = m_eventRegistry[handler->eventType];
eventHandlers.removeAll(handler);
if (eventHandlers.isEmpty()) {
m_eventRegistry.remove(handler->eventType);
}
delete handler;
}
m_subscriberIndex.remove(object);
disconnect(object, &QObject::destroyed,
this, &EventDispatcher::handleReceiverDestruction);
}
Usage Demonstration
Defining a Custom Event
#ifndef INPUTEVENT_H
#define INPUTEVENT_H
struct InputEvent
{
int keyCode;
unsigned int timestamp;
};
#endif // INPUTEVENT_H
Application Window Header
#ifndef APPLICATIONWINDOW_H
#define APPLICATIONWINDOW_H
#include <QMainWindow>
#include "EventDispatcher.h"
#include "InputEvent.h"
class ApplicationWindow : public QMainWindow
{
Q_OBJECT
public:
ApplicationWindow(QWidget *parent = nullptr);
~ApplicationWindow() override;
private slots:
void processInputEvent(const InputEvent &event);
private:
EventDispatcher *m_dispatcher;
};
#endif // APPLICATIONWINDOW_H
Application Window Implementation
#include "ApplicationWindow.h"
#include <QDebug>
ApplicationWindow::ApplicationWindow(QWidget *parent)
: QMainWindow(parent)
{
m_dispatcher = new EventDispatcher();
auto methodPointer = static_cast<void (QObject::*)(const InputEvent&)>(
&ApplicationWindow::processInputEvent);
m_dispatcher->registerHandler<InputEvent>(this, methodPointer);
InputEvent sampleEvent{ 65, 0 };
m_dispatcher->broadcast(sampleEvent);
}
ApplicationWindow::~ApplicationWindow()
{
delete m_dispatcher;
}
void ApplicationWindow::processInputEvent(const InputEvent &event)
{
qDebug() << "Received input event - Key:" << event.keyCode
<< "Time:" << event.timestamp;
}