Skip to content

Instantly share code, notes, and snippets.

@johnb003
Last active October 14, 2019 05:02
Show Gist options
  • Select an option

  • Save johnb003/dbc4a69af8ea8f4771666ce2e383047d to your computer and use it in GitHub Desktop.

Select an option

Save johnb003/dbc4a69af8ea8f4771666ce2e383047d to your computer and use it in GitHub Desktop.

Revisions

  1. johnb003 revised this gist Oct 14, 2019. 1 changed file with 15 additions and 8 deletions.
    23 changes: 15 additions & 8 deletions signals.h
    Original file line number Diff line number Diff line change
    @@ -5,21 +5,28 @@
    template <typename... FuncArgs>
    class Signal
    {
    using fp = std::function<void(FuncArgs...)>;
    std::forward_list<std::weak_ptr<fp> > registeredListeners;
    using Function = std::function<void(FuncArgs...)>;
    std::forward_list<std::weak_ptr<Function> > registeredListeners;

    public:
    using Listener = std::shared_ptr<fp>;
    using Listener = std::shared_ptr<Function>;

    Listener add(const std::function<void(FuncArgs...)> &cb) {
    Listener result(std::make_shared<fp>(cb));
    Listener add(Function cb) {
    // cb was passed by address. When creating the shared ptr, we copy it by value
    // cb is often an r value lambda so, we need to make sure we copy it.
    auto const result = std::make_shared<Function>(std::move(cb));
    registeredListeners.push_front(result);
    return result;
    }

    void raise(FuncArgs... args) {
    registeredListeners.remove_if([&args...](std::weak_ptr<fp> e) -> bool {
    // Don't be fooled by FuncArgs&& (It's not a r-value reference)
    // The redundant template specification actually changes this for perfect forwarding.
    // See: https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
    template <typename... FuncArgs>
    void raise(FuncArgs&&... args) {
    registeredListeners.remove_if([&args...](const std::weak_ptr<Function> &e) {
    if (auto f = e.lock()) {
    (*f)(args...);
    (*f)(std::forward<FuncArgs>(args)...);
    return false;
    }
    return true;
  2. johnb003 created this gist Oct 10, 2019.
    42 changes: 42 additions & 0 deletions signals.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    #include <forward_list>
    #include <functional>
    #include <memory>

    template <typename... FuncArgs>
    class Signal
    {
    using fp = std::function<void(FuncArgs...)>;
    std::forward_list<std::weak_ptr<fp> > registeredListeners;
    public:
    using Listener = std::shared_ptr<fp>;

    Listener add(const std::function<void(FuncArgs...)> &cb) {
    Listener result(std::make_shared<fp>(cb));
    registeredListeners.push_front(result);
    return result;
    }

    void raise(FuncArgs... args) {
    registeredListeners.remove_if([&args...](std::weak_ptr<fp> e) -> bool {
    if (auto f = e.lock()) {
    (*f)(args...);
    return false;
    }
    return true;
    });
    }
    };

    // To Use:

    // Store some signal provider
    // Signal<int> eventThing;

    // ...

    // Register a callback for the signal, and store the listener owning reference:
    // Signal<int>::Listener listener = eventThing.add([](int) { ... });

    // when `listener` goes out of scope, the callback will not get called anymore.
    // Likewise, if you change `listener`, such as to add it to a different Signal<int>,
    // it will automatically remove it from the original Signal.
    86 changes: 86 additions & 0 deletions test_signals.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    // Producer Example
    class A
    {
    public:
    int sourceVal;
    A(int i) : sourceVal(i) {}

    void Modify(int val) {
    sourceVal = val;
    bloopChanged.raise(val);
    }

    Signal<int> bloopChanged;
    };

    // Consumer Example
    class B
    {
    public:
    // Like a "slot"
    // You can assign the slot to the signal, and it handles disconnect when reset(), or assigned to a different signal.
    Signal<int>::Listener bloopResponse;
    int x;
    B() {
    x = 0;
    }
    ~B() {
    x = -99;
    }

    void set(int a) { x = a; }
    };

    void test() {
    A a1(1);
    A a2(2);
    A a3(3);

    // B scope
    {
    B b;

    auto cb = [&](int v) {
    b.set(v);
    };

    b.bloopResponse = a1.bloopChanged.add(cb);
    printf("As: (%d, %d, %d) B: %d\n", a1.sourceVal, a2.sourceVal, a3.sourceVal, b.x);

    a1.Modify(10);
    a2.Modify(20);
    a3.Modify(30);
    printf("As: (%d, %d, %d) B: %d\n", a1.sourceVal, a2.sourceVal, a3.sourceVal, b.x);

    b.bloopResponse = a2.bloopChanged.add(cb);

    a1.Modify(11);
    a2.Modify(22);
    a3.Modify(33);
    printf("As: (%d, %d, %d) B: %d\n", a1.sourceVal, a2.sourceVal, a3.sourceVal, b.x);

    }
    a1.Modify(100);
    a2.Modify(200);
    a3.Modify(300);
    printf("As: (%d, %d, %d) B: (gone)\n", a1.sourceVal, a2.sourceVal, a3.sourceVal);
    }

    // Prints:
    // As: (1, 2, 3) B: 0
    // As: (10, 20, 30) B: 10
    // As: (11, 22, 33) B: 22
    // As: (100, 200, 300) B: (gone)

    // Put a breakpoint in the labda, and you will see:
    // It only receives: 10, and 22, (it is not fired after leaving "B scope"

    // CAUTION:
    // This cannot protect against capturing an object in the lambda that goes out of scope.
    // If you need to, pass a weak_ptr in your lambda and check it:
    // [weak_ptr<YourObj> obj = your_shared_ptr_instance, other_captures]() {
    // if (auto o = obj.lock()) {
    // // stuff with o
    // // ...
    // }
    // });