Implementing an Event Bus Pattern in Qt for Decoupled Communication

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

Tags: Qt EventBus Design Pattern signals and slots Publish-Subscribe

Posted on Mon, 27 Jul 2026 16:20:00 +0000 by mtrp