2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C++】EventBusの基本! 疎結合なイベント処理を!

Last updated at Posted at 2025-12-16

Observerパターンは「1対多の通知」をシンプルに実現できる便利な仕組みです。
しかし、ゲームや大規模アプリケーションではイベントの種類や監視者が増え、管理が複雑になります。
そこでEventBusの仕組みを使用します。
EventBusは「イベントのハブ」として、発行者と購読者を疎結合に保ちながら柔軟なイベント処理を可能にします。
本記事ではObserverパターンを土台にしてイベントバスの仕組みを解説し、より大規模な設計へと進化させる方法を紹介します。

前提知識

  • C++の基礎文法を理解している
  • Observerパターンを理解している

Observerパターンをまだ知らない方は、先に上記の記事に目を通すことを進めます。

EventBusとは

EventBusは「イベント駆動型アーキテクチャ」を支える仕組みのひとつです。
発行者(Publisher)がイベントを投げると、購読者(Subscriber)が必要に応じてそれを受け取ります。 両者は直接依存せず、イベントバスというハブを介して通信するため、疎結合で柔軟な設計が可能になります。
新しいイベントや購読者を追加しても既存コードへの影響が少なく、大規模アプリケーションでの拡張性が高いというメリットがあります。
その反面で、イベントの流れが見えにくくデバッグが難しくなるというデメリットもあります。

Observerとの比較

項目 Observerパターン EventBus
通知方法 Subjectが直接Observerへ通知 バスが購読者へ一括通知
結合度 SubjectObserverがインターフェースで結合 PublisherSubscriberはバスのみ依存
適用範囲 小~中規模向け 中~大規模向け
可読性 イベントの流れが明示的で追いやすい イベントの流れが見えにくくデバッグが難しい

EventBusの構造と用語

基本的な構造は以下の3つの要素から成り立っています。

  • Publisher(発行者)
    • イベントを生成してバスに投げる役割
  • Subscriber(購読者)
    • バスからイベントを受け取り、必要な処理を行う役割
  • EventBus(イベントバス)
    • PublisherSubscriberの間を仲介するハブ
    • イベントを一元管理し、登録されたSubscriberに通知を届ける
    • PublisherSubscriberはバスに依存するだけで、互いを直接知らない

構造の図解

コード例

EventBusでプレイヤーのダメージ処理を管理するコード

EventBus.h
//--------------------------------------------------------------
//! @file	EventBus.h
//! @brief	EventBusの定義
//! @author つきの
//--------------------------------------------------------------
#pragma once
#include <string>
#include <functional>
#include <unordered_map>
#include <vector>
//--------------------------------------------------------------
//! @class	EventBusクラス
//! @note 	イベントの購読登録と発行を管理するクラス
//--------------------------------------------------------------
class EventBus {
public:
	using Callback = std::function<void(int)>;						// コールバック関数の型定義
public:
	//--------------------------------------------------------------
	//	イベント購読登録
	//! @param	event		[in] イベント名
	//! @param	callback	[in] コールバック関数
	//--------------------------------------------------------------
	void subscribe(const std::string& event, Callback callback);

	//--------------------------------------------------------------
	//	イベント発行
	//! @param	event		[in] イベント名
	//! @param	value		[in] イベント値
	//--------------------------------------------------------------
	void publish(const std::string& event, int value);
private:
	// 購読者リスト
	std::unordered_map<std::string, std::vector<Callback>> subscribers_;
};
EventBus.cpp
//--------------------------------------------------------------
//! @file	EventBus.cpp
//! @brief	EventBusの実装
//! @author つきの
//--------------------------------------------------------------
#include "EventBus.h"
//--------------------------------------------------------------
//! @brief	イベント購読登録
//--------------------------------------------------------------
void EventBus::subscribe(const std::string& event, Callback callback) {
	// イベント名に対応するコールバック関数リストに追加
	subscribers_[event].push_back(callback);
}

//--------------------------------------------------------------
//! @brief	イベント発行
//--------------------------------------------------------------
void EventBus::publish(const std::string& event, int value) {
	// イベント名に対応するコールバック関数リストを取得
	auto it = subscribers_.find(event);
	// コールバック関数を呼び出す
	if (it != subscribers_.end()) {
		// 登録されているすべてのコールバック関数を呼び出す
		for (auto& cb : it->second) {
			cb(value);
		}
	}
}
Player.h
//---------------------------------------------------
//! @file	Player.h
//! @brief	Playerの定義
//! @author つきの
//---------------------------------------------------
#pragma once
//---------------------------------------------------
//! @brief	 Playerクラス
//! @details イベントバスを利用してイベントを発行するプレイヤークラス
//---------------------------------------------------
#include "EventBus.h"
class Player {
public:
	//---------------------------------------------------
	// コンストラクタ
	//! @param	hp		[in] 初期体力
	//! @param	bus		[in] イベントバスの参照
	//---------------------------------------------------
	Player(int hp, EventBus& bus);

	//---------------------------------------------------
	// ダメージを受ける
	//! @param	damage	[in] ダメージ量
	//---------------------------------------------------
	void takeDamage(int damage);
private:
	int hp_;                 //!< プレイヤーの体力
	EventBus& bus_;        //!< イベントバスの参照
};
Player.cpp
//---------------------------------------------------
//! @file	Player.cpp
//! @brief	Playerの実装
//! @author つきの
//---------------------------------------------------
#include "Player.h"
//---------------------------------------------------
//! @brief	コンストラクタ
//---------------------------------------------------
Player::Player(int hp, EventBus& bus)
	: hp_(hp), bus_(bus) {
}

//---------------------------------------------------
//! @brief	ダメージを受ける
//---------------------------------------------------
void Player::takeDamage(int damage) {
	hp_ -= damage;	// 体力を減少
	// 体力が0未満にならないように補正
	if (hp_ < 0) {
		hp_ = 0;
	}
	bus_.publish("HP_CHANGED", hp_); // EventBusに通知
}
Logger.h
//--------------------------------------------------------------
//! @file	Logger.h
//! @brief	Loggerの定義
//! @author つきの
//--------------------------------------------------------------
#pragma once
#include "EventBus.h"
//--------------------------------------------------------------
//! @class	Loggerクラス
//! @note 	イベントバスにログ出力の処理を登録する
//--------------------------------------------------------------
class Logger {
public:
	//--------------------------------------------------------------
	// イベントバスに処理を登録
	//! @param	bus		[in] イベントバスの参照
	//--------------------------------------------------------------
	void registerTo(EventBus& bus);
};
Logger.cpp
//--------------------------------------------------------------
//! @file	Logger.cpp
//! @brief	Loggerの実装
//! @author つきの
//--------------------------------------------------------------
#include "Logger.h"
#include <iostream>
//--------------------------------------------------------------
//! @brief イベントバスに処理を登録
//--------------------------------------------------------------
void Logger::registerTo(EventBus& bus) {
	// ログ出力用のコールバック関数を定義
	auto callback = [](int value) {
		std::cout << "[Log] Event=HP_CHANGED, Value=" << value << std::endl;
	};
	// HP_CHANGEDイベントに購読登録
	bus.subscribe("HP_CHANGED", callback);
}
SoundSystem.h
//--------------------------------------------------------------
//! @file	SoundSystem.h
//! @brief	サウンドシステムの定義
//! @author つきの
//--------------------------------------------------------------
#pragma once
#include "EventBus.h"
//--------------------------------------------------------------
//! @class	SoundSystemクラス
//! @note 	イベントバスに音声の処理を登録する
//--------------------------------------------------------------
class SoundSystem {
public:
	//--------------------------------------------------------------
	// イベントバスに処理を登録
	//! @param	bus		[in] イベントバスの参照
	//--------------------------------------------------------------
	void registerTo(EventBus& bus);
};
SoundSystem.cpp
//--------------------------------------------------------------
//! @file	SoundSystem.cpp
//! @brief	サウンドシステムの実装
//! @author つきの
//--------------------------------------------------------------
#include "SoundSystem.h"
#include <iostream>
//--------------------------------------------------------------
//! @brief イベントバスに処理を登録
//--------------------------------------------------------------
void SoundSystem::registerTo(EventBus& bus) {
	// サウンド再生用のコールバック関数を定義
	auto callback = [](int value) {
		std::cout << "[Sound] 再生: HP_CHANGED " << value << std::endl;
	};
	// HP_CHANGEDイベントに購読登録
	bus.subscribe("HP_CHANGED", callback);
}
main.cpp
//--------------------------------------------------------------
//! @file	main.cpp
//! @brief  ゲーム開発を模したイベントバスの簡易的なサンプル
//! @author つきの
//--------------------------------------------------------------
#include "EventBus.h"
#include "Player.h"
#include "Logger.h"
#include "SoundSystem.h"
//エントリポイント
int main() {
	// イベントバスの作成
	EventBus bus;

	// プレイヤーの作成
	Player player(100, bus);

	// ロガーとサウンドシステムの作成と処理の登録
	Logger logger;
	logger.registerTo(bus);
	SoundSystem soundSystem;
	soundSystem.registerTo(bus);

	// プレイヤーがダメージを受けるシミュレーション
	player.takeDamage(20);
	//終了
	return 0;
}
result
[Log] Event=HP_CHANGED, Value=80
[Sound] 再生: HP_CHANGED 80

クラス図

subscribe/購読

EventBussubscribe()から、ラムダ式を登録します。
登録したラムダ式はEventBusが管理を行います。
本記事ではキーをイベント名(std::string)で管理していますが、キーを自作型にすることで拡張を行うこともできます。

EventBus.h
//--------------------------------------------------------------
//! @class	EventBusクラス
//! @note 	イベントの購読登録と発行を管理するクラス
//--------------------------------------------------------------
class EventBus {
public:
	using Callback = std::function<void(int)>;						// コールバック関数の型定義
public:
	//--------------------------------------------------------------
	//	イベント購読登録
	//! @param	event		[in] イベント名
	//! @param	callback	[in] コールバック関数
	//--------------------------------------------------------------
	void subscribe(const std::string& event, Callback callback);

    // 中略
private:
	// 購読者リスト
	std::unordered_map<std::string, std::vector<Callback>> subscribers_;
};
EventBus.cpp
//--------------------------------------------------------------
//! @brief	イベント購読登録
//--------------------------------------------------------------
void EventBus::subscribe(const std::string& event, Callback callback) {
	// イベント名に対応するコールバック関数リストに追加
	subscribers_[event].push_back(callback);
}

publish/発行

EventBuspublish()を呼ぶことで、管理しているコールバック関数(本記事ではsubscribers_)の中からキー(本記事ではconst std::string& event)が対応するコールバックを発火させます。

EventBus.h
//--------------------------------------------------------------
//! @class	EventBusクラス
//! @note 	イベントの購読登録と発行を管理するクラス
//--------------------------------------------------------------
class EventBus {
public:
    // 中略
    
	//--------------------------------------------------------------
	//	イベント発行
	//! @param	event		[in] イベント名
	//! @param	value		[in] イベント値
	//--------------------------------------------------------------
	void publish(const std::string& event, int value);
private:
	// 購読者リスト
	std::unordered_map<std::string, std::vector<Callback>> subscribers_;
};
EventBus.cpp
//--------------------------------------------------------------
//! @brief	イベント発行
//--------------------------------------------------------------
void EventBus::publish(const std::string& event, int value) {
	// イベント名に対応するコールバック関数リストを取得
	auto it = subscribers_.find(event);
	// コールバック関数を呼び出す
	if (it != subscribers_.end()) {
		// 登録されているすべてのコールバック関数を呼び出す
		for (auto& cb : it->second) {
			cb(value);
		}
	}
}

ObserverパターンでいうSubjectに当たるクラス(イベントの通知元になるクラス、本記事ではPlayer)は、EventBusのポインタか参照を持つことで、クラス内からpublishを呼ぶことが出来ます。

Player.h
class Player {
public:
	//---------------------------------------------------
	// コンストラクタ
	//! @param	hp		[in] 初期体力
	//! @param	bus		[in] イベントバスの参照
	//---------------------------------------------------
	Player(int hp, EventBus& bus);
    //---------------------------------------------------
    // ダメージを受ける
    //! @param	damage	[in] ダメージ量
    //---------------------------------------------------
    void takeDamage(int damage);
private:
	int hp_;                 //!< プレイヤーの体力
	EventBus& bus_;        //!< イベントバスの参照
};
Player.cpp
//---------------------------------------------------
//! @brief	コンストラクタ
//---------------------------------------------------
Player::Player(int hp, EventBus& bus)
	: hp_(hp), bus_(bus) {
}

//---------------------------------------------------
//! @brief	ダメージを受ける
//---------------------------------------------------
void Player::takeDamage(int damage) {
	hp_ -= damage;	// 体力を減少
	// 体力が0未満にならないように補正
	if (hp_ < 0) {
		hp_ = 0;
	}
	bus_.publish("HP_CHANGED", hp_); // EventBusに通知
}

発展形

EventBusの発展形をリポジトリ付きで解説した記事もあるので、気になる方はぜひ目を通してみて下さい。

総括

  • EventBusを使用することで、疎結合なイベント駆動設計を実現できる
  • イベント駆動にすると流れがつかみにくく、デバッグが少々難しくなるデメリットが隠れている
  • subscribeでコールバックを登録し、publishで登録したコールバックを発火させる仕組みになっている
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?