patterncppMinor
C++ 14 event bus implementation
Viewed 0 times
implementationbusevent
Problem
The code allows you to subscribe to events and publish events via an
I don't particularly like the fact that:
I would like to know if there are any improvements I could make (I am reasonably new to C++ but am experienced in other languages).
Example use
Main event bus source
```
#pragma once
#include
#include
#include
#include
#include
template
using EventHandler = std::function;
template
using IndexedEventHandler = std::function;
template
using EventDataType = typename std::remove_reference::type>::type;
class EventIdentifier
{
private:
int _id;
public:
EventIdentifier()
{
static std::atomic_int counter = 1;
_id = counter++;
}
bool operator ==(const EventIdentifier &other) const
{
return _id == other._id;
}
bool operator
class EventSubject fi
EventBus class. Events are identified by a source and subject. Events can have a single parameter or they can be 'indexed' and have two parameters (the first being an integer index - I will be using this a lot so wanted a specialization).I don't particularly like the fact that:
- I need to have an
IndexedSubscriptionmethod - it would be nicer to overload theSubscriptionmethod, but the compiler doesn't like that.
- The template argument needs to be specified when calling
SubscriptionandIndexedSubscription.
I would like to know if there are any improvements I could make (I am reasonably new to C++ but am experienced in other languages).
Example use
#include
#include
#include "EventBus.h"
class Sources
{
public:
static EventSource Source1()
{
static EventSource source;
return source;
}
static EventSource Source2()
{
static EventSource source;
return source;
}
};
class Subjects
{
public:
static EventSubject Subject1()
{
static EventSubject subject;
return subject;
}
};
int main()
{
EventBus eb;
eb.Subscribe(Sources::Source1(), Subjects::Subject1(), [](double& data)
{
std::cout (Sources::Source1(), Subjects::Subject1(), 33);
std::cout << "Done... ";
std::getchar();
return EXIT_SUCCESS;
}Main event bus source
```
#pragma once
#include
#include
#include
#include
#include
template
using EventHandler = std::function;
template
using IndexedEventHandler = std::function;
template
using EventDataType = typename std::remove_reference::type>::type;
class EventIdentifier
{
private:
int _id;
public:
EventIdentifier()
{
static std::atomic_int counter = 1;
_id = counter++;
}
bool operator ==(const EventIdentifier &other) const
{
return _id == other._id;
}
bool operator
class EventSubject fi
Solution
Missing includes
The header needs
And the demo program needs
(or - better - just omit the final
It's not clear why
Instead of transforming types like that, it's probably better to constrain the template functions to reject reference types as arguments.
I don't like laundering event data through
I think we could avoid casting, by using separate maps for subscriptions of different types.
Consider making
I see no way to unsubscribe from this publisher. That's problematic, particularly if we have subscriptions which have captured references.
Instead of the separate
The test program looks very long-winded when creating source and subject identifiers. It can be simplified:
We needn't worry about the exact order in which these globals are initialised; any order is fine.
Consider adding names to our sources and subjects, because debugging gets hard when events are only identified by integers (and not easily predictable ones at that!).
The test program shouldn't exit with a partially-written line of output - add
The header needs
#include
#include And the demo program needs
#include (or - better - just omit the final
std::getchar() whose result is never used and whose error is ignored)It's not clear why
EventDataType removes references twice - C++ doesn't have references to references.Instead of transforming types like that, it's probably better to constrain the template functions to reject reference types as arguments.
IndexedData::_data looks very dangerous, as it's a reference that's initialised from an rvalue-reference argument, so could very easily be invalidated.I don't like laundering event data through
void*, but you seem to have protected against accidents by carrying the type with the Subject class.I think we could avoid casting, by using separate maps for subscriptions of different types.
EventBus::EventObserver has a virtual function, so we should provide it with a public virtual destructor for safety. Also, I don't think it makes sense for the Notify to modify the observer itself:struct EventObserver
{
virtual ~EventObserver() = default;
virtual void Notify(void* data) const = 0;
};Consider making
EventBus::EventSubscription final, which could improve the compiler's ability to optimise.I see no way to unsubscribe from this publisher. That's problematic, particularly if we have subscriptions which have captured references.
Instead of the separate
Indexed versions of functions, consider generalising to take any number of data arguments, and forwarding them as a parameter pack. That reduces code and provides more user flexibility!The test program looks very long-winded when creating source and subject identifiers. It can be simplified:
namespace Sources
{
const EventSource Source1;
const EventSource Source2;
}
namespace Subjects
{
const EventSubject Subject1;
}We needn't worry about the exact order in which these globals are initialised; any order is fine.
Consider adding names to our sources and subjects, because debugging gets hard when events are only identified by integers (and not easily predictable ones at that!).
The test program shouldn't exit with a partially-written line of output - add
\n to the "Done" string.Code Snippets
#include <type_traits>
#include <utility>#include <cstdio>struct EventObserver
{
virtual ~EventObserver() = default;
virtual void Notify(void* data) const = 0;
};namespace Sources
{
const EventSource Source1;
const EventSource Source2;
}
namespace Subjects
{
const EventSubject<double> Subject1;
}Context
StackExchange Code Review Q#109987, answer score: 2
Revisions (0)
No revisions yet.