Event-driven архитектура на C++: как написать реактивный код

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Зачем вам event-driven архитектура на C++?

Представьте, что вы — дирижер оркестра. Вы не играете на каждом инструменте сами, но взмах вашей палочки запускает цепь реакций: скрипки начинают петь, барабаны отбивают ритм. Event-driven архитектура (событийно-ориентированная архитектура) работает по тому же принципу — это подход, при котором система реагирует на внешние или внутренние события, а не выполняет заранее заданный линейный сценарий.

В мире C++ этот стиль программирования становится особенно ценным, когда нужно писать быстрые, масштабируемые и отзывчивые приложения. Игровые движки, графические интерфейсы, высоконагруженные серверы — везде, где важна реакция на действия пользователя или изменение состояния, event-driven подход незаменим.

В этой статье мы не просто разберем теорию, а напишем реальный код, который покажет, как событийно-ориентированное программирование на C++ превращает скучный последовательный код в живой, реактивный организм.

Что такое event-driven архитектура простыми словами

Event-driven architecture — это архитектурный паттерн, в котором компоненты системы общаются через события. Вместо того чтобы активно опрашивать состояние объекта (polling), вы просто подписываетесь на события, которые вас интересуют, и реагируете на них.

Ключевые элементы событийной модели

  • Event (событие) — сигнал о том, что что-то произошло (кликнули кнопку, пришел пакет, изменилась температура)
  • Event producer (источник) — тот, кто генерирует событие
  • Event consumer (подписчик) — тот, кто обрабатывает событие
  • Event bus (шина событий) — центральный маршрутизатор, который доставляет события подписчикам
  • Event loop (цикл событий) — бесконечный цикл, который ждет и диспетчеризует события

Представьте себе умный дом. Датчик движения (источник) генерирует событие "Движение обнаружено". Шина событий передает это событие подписчикам: камере (начать запись) и лампе (включить свет). Никто никого не опрашивает — все происходит автоматически.

Почему C++ идеален для event-driven систем

Некоторые могут подумать: "C++ — это же низкоуровневый язык, зачем он для реактивного программирования?" На самом деле, событийно-ориентированное программирование на C++ дает уникальные преимущества:

  • Максимальная производительность — минимальные накладные расходы на диспетчеризацию событий
  • Полный контроль над памятью — вы сами решаете, как управлять ресурсами
  • Поддержка многопоточности — C++ позволяет строить распределенные системы событий
  • Шаблоны и лямбды — мощные инструменты для гибкой обработки событий

Пишем event-driven систему на C++ с нуля

Давайте создадим простую, но рабочую систему событий. Мы будем использовать паттерн Observer — классический способ реализации событийной модели.

Базовая реализация: EventBus

Начнем с создания центральной шины событий, которая будет хранить подписчиков и рассылать события.

#include <iostream>
#include <functional>
#include <map>
#include <vector>
#include <any>
#include <string>

// Тип события — просто строка для простоты
using EventType = std::string;

// Тип обработчика — функция, принимающая std::any
using EventHandler = std::function<void(const std::any&)>;

class EventBus {
private:
    std::map<EventType, std::vector<EventHandler>> handlers_;

public:
    // Подписка на событие
    void subscribe(const EventType& event, EventHandler handler) {
        handlers_[event].push_back(handler);
    }

    // Публикация события
    void publish(const EventType& event, const std::any& data = {}) {
        auto it = handlers_.find(event);
        if (it != handlers_.end()) {
            for (auto& handler : it->second) {
                handler(data);
            }
        }
    }
};

Это минимальная, но полностью рабочая реализация event bus на C++. Давайте посмотрим, как она работает в деле.

Пример использования: система уведомлений

// Пример структуры данных для события
struct UserAction {
    std::string username;
    std::string action;
    int timestamp;
};

int main() {
    EventBus bus;

    // Подписываемся на событие логина
    bus.subscribe("user_login", [](const std::any& data) {
        auto action = std::any_cast<UserAction>(data);
        std::cout << "[LOG] Пользователь " << action.username 
                  << " вошел в систему." << std::endl;
    });

    // Подписываемся на событие выхода
    bus.subscribe("user_logout", [](const std::any& data) {
        auto action = std::any_cast<UserAction>(data);
        std::cout << "[LOG] Пользователь " << action.username 
                  << " вышел." << std::endl;
    });

    // Публикуем события
    bus.publish("user_login", UserAction{"alice", "login", 12345});
    bus.publish("user_logout", UserAction{"bob", "logout", 12346});

    return 0;
}

Вывод программы:

[LOG] Пользователь alice вошел в систему.
[LOG] Пользователь bob вышел.

Продвинутые техники: асинхронная обработка событий

В реальных приложениях события часто приходят асинхронно. Давайте добавим event loop с очередью событий, чтобы обрабатывать их в правильном порядке.

#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>

class AsyncEventBus : public EventBus {
private:
    struct QueuedEvent {
        EventType type;
        std::any data;
    };

    std::queue<QueuedEvent> eventQueue_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool running_ = true;

public:
    // Асинхронная публикация
    void publishAsync(const EventType& event, const std::any& data = {}) {
        std::lock_guard<std::mutex> lock(mtx_);
        eventQueue_.push({event, data});
        cv_.notify_one();
    }

    // Запуск event loop
    void run() {
        while (running_) {
            std::unique_lock<std::mutex> lock(mtx_);
            cv_.wait(lock, [this] { return !eventQueue_.empty() || !running_; });
            
            if (!running_) break;

            QueuedEvent ev = eventQueue_.front();
            eventQueue_.pop();
            lock.unlock();

            // Обработка события
            publish(ev.type, ev.data);
        }
    }

    void stop() {
        running_ = false;
        cv_.notify_one();
    }
};

Те

Блоги

Рекомендаций по книгам