2
\$\begingroup\$

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

\$\endgroup\$
  • 1
    \$\begingroup\$ To me one sender can have more than one signal. And signalling from some global ObserverSystem is not really following other signal-slot framework implementations as well. I cannot see the proper link between one sender and one receiver. \$\endgroup\$ – Alexander V Dec 29 '18 at 17:32
  • \$\begingroup\$ @AlexanderV ObserverSystem is just the manager of all the functionality. A sender would use the function emitSignal and since he can use any signal-types he can practically have multiple signals. I would have made the ObserverSystem 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 since ObserverSystem is just the manager of the functions it is not a big problem, that it is global. \$\endgroup\$ – Darius Duesentrieb Dec 30 '18 at 14:29
  • 1
    \$\begingroup\$ Having to create a new type for every event (plus possibly needing to wrap the handler in a member function) seem like a lot of boilerplate. Also, this implementation doesn't seem to work with lambdas or other callable objects. \$\endgroup\$ – hoffmale Dec 31 '18 at 17:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.