Last active
October 14, 2019 05:02
-
-
Save johnb003/dbc4a69af8ea8f4771666ce2e383047d to your computer and use it in GitHub Desktop.
Very lightweight Signals / Slots mechanism for C++, with assumed return void. (No need to aggregate).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <forward_list> | |
#include <functional> | |
#include <memory> | |
template <typename... FuncArgs> | |
class Signal | |
{ | |
using Function = std::function<void(FuncArgs...)>; | |
std::forward_list<std::weak_ptr<Function> > registeredListeners; | |
public: | |
using Listener = std::shared_ptr<Function>; | |
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; | |
} | |
// 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)(std::forward<FuncArgs>(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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
// // ... | |
// } | |
// }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment