Signals are represented as types. You can connect a member function of some instance to the Observer system. This connected function will be called whenever a signal of a type that is the same type as the function parameter is emitted.
ObserverSystem.hpp:
#include <vector>
class ObserverSystem
{
public:
template<typename SignalType>
inline static void emitSignal(const SignalType& signal)
{
for(auto f : callEverything<SignalType>)
{
f(signal);
}
}
template<typename SignalType, typename ObjectType>
inline static void connect(ObjectType& object, void (ObjectType::*function)(const SignalType& signal))
{
bool add = true;
for(auto& entry : functions<SignalType, ObjectType>)
{
if(entry.function == function && entry.object == &object)
{
add = false;
break;
}
}
if(add)
{
functions<SignalType, ObjectType>.emplace_back(ObjectFunction<SignalType, ObjectType>{&object, function});
}
add = true;
for(auto& entry : callEverything<SignalType>)
{
if(entry == callAll<SignalType, ObjectType>)
{
add = false;
break;
}
}
if(add)
{
callEverything<SignalType>.emplace_back(callAll<SignalType, ObjectType>);
}
}
template<typename SignalType, typename ObjectType>
inline static void disconnect(ObjectType& object, void (ObjectType::*function)(const SignalType& signal))
{
for(size_t i = 0; i < functions<SignalType, ObjectType>.size();)
{
if(functions<SignalType, ObjectType>[i].function == function && functions<SignalType, ObjectType>[i].object == &object)
{
functions<SignalType, ObjectType>.erase(functions<SignalType, ObjectType>.begin() + i);
}
else
{
i++;
}
}
}
private:
template<typename SignalType, typename ObjectType>
struct ObjectFunction
{
ObjectType* object;
void (ObjectType::*function)(const SignalType& signal);
void call(const SignalType& signal)
{
(object->*function)(signal);
}
};
template<typename SignalType, typename ObjectType>
inline static std::vector<ObjectFunction<SignalType, ObjectType>> functions;
template<typename SignalType, typename ObjectType>
inline static void callAll(const SignalType& signal)
{
for(auto function : functions<SignalType, ObjectType>)
{
function.call(signal);
}
}
template<typename SignalType>
inline static std::vector<void (*)(const SignalType& signal)> callEverything;
};
main.cpp
#include <iostream>
#include "ObserverSystem.hpp"
struct SignalTypeOne
{
int b;
};
struct SignalTypeTwo
{
int x;
int y;
};
struct Hello
{
int a = 123;
void f(const SignalTypeOne& signal)
{
std::cout << (int*)this << " : a: " << a << std::endl;
std::cout << "b: " << signal.b << std::endl;
}
};
struct Some
{
int vx = 0;
int vy = 0;
void f1(const SignalTypeTwo& signal)
{
vx += signal.x;
vy += signal.y;
}
void f2(const SignalTypeOne& signal)
{
std::cout << (int*)this << ": " << "vx = " << vx <<", vy = " << vy << std::endl;
}
};
int main()
{
Hello h;
Hello g = Hello{321};
Some v1;
Some v2;
ObserverSystem::connect(h, &Hello::f);
ObserverSystem::connect(g, &Hello::f);
ObserverSystem::connect(v1, &Some::f1);
ObserverSystem::connect(v2, &Some::f1);
ObserverSystem::connect(v1, &Some::f2);
ObserverSystem::connect(v2, &Some::f2);
ObserverSystem::emitSignal(SignalTypeTwo{1,1});
ObserverSystem::emitSignal(SignalTypeOne{444});
ObserverSystem::disconnect(h, &Hello::f);
ObserverSystem::disconnect(v1, &Some::f1);
ObserverSystem::emitSignal(SignalTypeTwo{1,2});
ObserverSystem::emitSignal(SignalTypeOne{555});
ObserverSystem::connect(h, &Hello::f);
ObserverSystem::emitSignal(SignalTypeTwo{2,1});
ObserverSystem::emitSignal(SignalTypeOne{666});
ObserverSystem::disconnect(h, &Hello::f);
ObserverSystem::disconnect(g, &Hello::f);
ObserverSystem::disconnect(v2, &Some::f1);
ObserverSystem::disconnect(v1, &Some::f2);
ObserverSystem::disconnect(v2, &Some::f2);
ObserverSystem::emitSignal(SignalTypeTwo{100,100});
ObserverSystem::emitSignal(SignalTypeOne{777});
return 0;
}
Exemplary output:
0x7ffe51692a18 : a: 123
b: 444
0x7ffe51692a10 : a: 321
b: 444
0x7ffe51692a08: vx = 1, vy = 1
0x7ffe51692a00: vx = 1, vy = 1
0x7ffe51692a10 : a: 321
b: 555
0x7ffe51692a08: vx = 1, vy = 1
0x7ffe51692a00: vx = 2, vy = 3
0x7ffe51692a10 : a: 321
b: 666
0x7ffe51692a18 : a: 123
b: 666
0x7ffe51692a08: vx = 1, vy = 1
0x7ffe51692a00: vx = 4, vy = 4
A function should always get disconnected when it gets destroyed.
EDIT:
Just added free functions: https://gitlab.com/tsoj/observersystem
ObserverSystem
is just the manager of all the functionality. A sender would use the functionemitSignal
and since he can use any signal-types he can practically have multiple signals. I would have made theObserverSystem
an instanced Object (so that all the public functions are member functions, not statics). However, this seems impossible in C++17 because template member variables are currently not allowed. It may be possible in C++23 with reflection and injection. But sinceObserverSystem
is just the manager of the functions it is not a big problem, that it is global. \$\endgroup\$