So I have this function which is behaving like the setInterval function in JS. I found it here.
I am currently trying to change it so it can be stopped. I do not fully understand the behavior of this code.
void setInterval(function<void(void)> func, unsigned int interval) {
thread([func, interval]() {
while (1) {
auto x = chrono::steady_clock::now() + chrono::milliseconds(interval);
func();
this_thread::sleep_until(x);
}
}).detach();
}
I tried it like this:
void setInterval(function<void(void)> func, unsigned int interval, bool &b) {
thread([func, interval, *b]() {
while (*b) {
auto x = chrono::steady_clock::now() + chrono::milliseconds(interval);
func();
this_thread::sleep_until(x);
}
}).detach();
}
(this won't compile), and in main calling it like this:
bool B;
setInterval(myFunction,1000,B);
I was expecting that if I change the B variable to false, then the thread in setInterval function stops, but I haven't managed to reach my goal like this. Any idead/suggestions? Thank you in advance.
Sorry, but I didn't find a design simpler than that.
You could, make a class that owns both a thread, and a weak_ptr to itself,
to be a "holder" that the callable can see it safely, because the callable
will still exists even if the object is destructed. You don't want a dangling pointer.
template<typename T>
struct IntervalRepeater {
using CallableCopyable = T;
private:
weak_ptr<IntervalRepeater<CallableCopyable>> holder;
std::thread theThread;
IntervalRepeater(unsigned int interval,
CallableCopyable callable): callable(callable), interval(interval) {}
void thread() {
weak_ptr<IntervalRepeater<CallableCopyable>> holder = this->holder;
theThread = std::thread([holder](){
// Try to strongify the pointer, to make it survive this loop iteration,
// and ensure that this pointer is valid, if not valid, end the loop.
while (shared_ptr<IntervalRepeater<CallableCopyable>> ptr = holder.lock()) {
auto x = chrono::steady_clock::now() + chrono::milliseconds(ptr->interval);
ptr->callable();
this_thread::sleep_until(x);
}
});
}
public:
const CallableCopyable callable;
const unsigned int interval;
static shared_ptr<IntervalRepeater<T>> createIntervalRepeater(unsigned int interval,
CallableCopyable callable) {
std::shared_ptr<IntervalRepeater<CallableCopyable>> ret =
shared_ptr<IntervalRepeater<CallableCopyable>>(
new IntervalRepeater<CallableCopyable>(interval, callable));
ret->holder = ret;
ret->thread();
return ret;
}
~IntervalRepeater() {
// Detach the thread before it is released.
theThread.detach();
}
};
void beginItWaitThenDestruct() {
auto repeater = IntervalRepeater<function<void()>>::createIntervalRepeater(
1000, [](){ cout << "A second\n"; });
std::this_thread::sleep_for(std::chrono::milliseconds(3700));
}
int main() {
beginItWaitThenDestruct();
// Wait for another 2.5 seconds, to test whether there is still an effect of the object
// or no.
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
return 0;
}
C++ is not JavaScript, but C++ can apply most programming paradigms in different languages.
Related
Problem
I believe the following code should lead to runtime issues, but it doesn't. I'm trying to update the underlying object pointed to by the shared_ptr in one thread, and access it in another thread.
struct Bar {
Bar(string tmp) {
var = tmp;
}
string var;
};
struct Foo {
vector<Bar> vec;
};
std::shared_ptr<Foo> p1, p2;
std::atomic<bool> cv1, cv2;
void fn1() {
for(int i = 0 ; i < p1->vec.size() ; i++) {
cv2 = false;
cv1.wait(true);
std::cout << p1->vec.size() << " is the new size\n";
std::cout << p1->vec[i].var.data() << "\n";
}
}
void fn2() {
cv2.wait(true);
p2->vec = vector<Bar>();
cv1 = false;
}
int main()
{
p1 = make_shared<Foo>();
p1->vec = vector<Bar>(2, Bar("hello"));
p2 = p1;
cv1 = true;
cv2 = true;
thread t1(fn1);
thread t2(fn2);
t2.join();
t1.join();
}
Description
weirdly enough, the output is as follows. prints the new size as 0 (empty), but is still able to access the first element from the previous vector.
0 is the new size
hello
Is my understanding that the above code is not thread safe correct? am I missing something?
OR
According to the docs
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object.
Since I'm using ->/* member functions, does it mean that the code is thread safe? This part is kind of confusing as I'm performing read and write simultaneously without synchronization.
As for the shared_ptr:
In general, you can call all member functions of DIFFERENT instances of the shared_ptr from multiple threads without synchronization. However, if you want to call these functions from multiple threads on the SAME shared_ptr instance then it may lead to a race condition. When we talk about thread safety guarantee in the case of shrared_ptr, it is only guaranteed for the internals of the shared_ptr as explained above NOT FOR THE underlying object.
Having that said, consider the following code and read the comments. You can also play with it here: https://godbolt.org/z/8hvcW19q9
#include <memory>
#include <mutex>
#include <thread>
std::mutex widget_mutex;
class Widget
{
std::string value;
public:
void set_value(const std::string& str) { value = str; }
};
//This is not safe, you're calling member function of the same instance, taken by ref
void mt_reset_not_safe(std::shared_ptr<Widget>& w)
{
w.reset(new Widget());
}
//This is safe, you have a separate instance of shared_ptr
void mt_reset_safe(std::shared_ptr<Widget> w)
{
w.reset(new Widget());
}
//This is not safe, underlying object is not protected from race conditions
void mt_set_value_not_safe(std::shared_ptr<Widget> w)
{
w->set_value("Test value, test value");
}
//This is safe, we use mutex to safetly update the underlying object
void mt_set_value_safe(std::shared_ptr<Widget> w)
{
auto lock = std::scoped_lock{widget_mutex};
w->set_value("Test value, test value");
}
template<class Callable, class... Args>
void run(Callable callable, Args&&... args)
{
auto th1 = std::thread(callable, std::forward<Args>(args)...);
auto th2 = std::thread(callable, std::forward<Args>(args)...);
th1.join();
th2.join();
}
void run_not_safe_reset()
{
auto widget = std::make_shared<Widget>();
run(mt_reset_not_safe, std::ref(widget));
}
void run_safe_reset()
{
auto widget = std::make_shared<Widget>();
run(mt_reset_safe, widget);
}
void run_mt_set_value_not_safe()
{
auto widget = std::make_shared<Widget>();
run(mt_set_value_not_safe, widget);
}
void run_mt_set_value_safe()
{
auto widget = std::make_shared<Widget>();
run(mt_set_value_safe, widget);
}
int main()
{
//Uncommne to see the result
// run_not_safe_reset();
// run_safe_reset();
// run_mt_set_value_not_safe();
// run_mt_set_value_safe();
}
I am trying to work with Coroutines and multithreading together in C++.
In many coroutine examples, they create a new thread in the await_suspend of the co_await operator for the promise type. I want to submit to a thread pool in this function.
Here I define a co_await for future<int>.
void await_suspend(std::coroutine_handle<> handle) {
this->wait();
handle.resume();
}
I want to change this code to submit a lambda/function pointer to a threadpool. Potentially I can use Alexander Krizhanovsky's ringbuffer to communicate with the threadpool to create a threadpool by myself or use boost's threadpool.
My problem is NOT the thread pool. My problem is that I don't know how to get reference to the threadpool in this co_await operator.
How do I pass data from the outside environment where the operator is to this await_suspend function? Here is an example of what I want to do:
void await_suspend(std::coroutine_handle<> handle) {
// how do I get "pool"? from within this function
auto res = pool.enqueue([](int x) {
this->wait();
handle.resume();
});
}
I am not an expert at C++ so I'm not sure how I would get access to pool in this operator?
Here's the full code inspired by this GitHub gist A simple C++ coroutine example.
#include <future>
#include <iostream>
#include <coroutine>
#include <type_traits>
#include <list>
#include <thread>
using namespace std;
template <>
struct std::coroutine_traits<std::future<int>> {
struct promise_type : std::promise<int> {
future<int> get_return_object() { return this->get_future(); }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(int value) { this->set_value(value); }
void unhandled_exception() {
this->set_exception(std::current_exception());
}
};
};
template <>
struct std::coroutine_traits<std::future<int>, int> {
struct promise_type : std::promise<int> {
future<int> get_return_object() { return this->get_future(); }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(int value) { this->set_value(value); }
void unhandled_exception() {
this->set_exception(std::current_exception());
}
};
};
auto operator co_await(std::future<int> future) {
struct awaiter : std::future<int> {
bool await_ready() { return false; } // suspend always
void await_suspend(std::coroutine_handle<> handle) {
this->wait();
handle.resume();
}
int await_resume() { return this->get(); }
};
return awaiter{std::move(future)};
}
future<int> async_add(int a, int b)
{
auto fut = std::async([=]() {
int c = a + b;
return c;
});
return fut;
}
future<int> async_fib(int n)
{
if (n <= 2)
co_return 1;
int a = 1;
int b = 1;
// iterate computing fib(n)
for (int i = 0; i < n - 2; ++i)
{
int c = co_await async_add(a, b);
a = b;
b = c;
}
co_return b;
}
future<int> test_async_fib()
{
for (int i = 1; i < 10; ++i)
{
int ret = co_await async_fib(i);
cout << "async_fib(" << i << ") returns " << ret << endl;
}
}
int runfib(int arg) {
auto fut = test_async_fib();
fut.wait();
return 0;
}
int run_thread() {
printf("Running thread");
return 0;
}
int main()
{
std::list<shared_ptr<std::thread>> threads = { };
for (int i = 0 ; i < 10; i++) {
printf("Creating thread\n");
std::shared_ptr<std::thread> thread = std::make_shared<std::thread>(runfib, 5);
threads.push_back(thread);
}
std::list<shared_ptr<std::thread>>::iterator it;
for (it = threads.begin(); it != threads.end(); it++) {
(*it).get()->join();
printf("Joining thread");
}
fflush(stdout);
return 0;
}
You could have a thread pool, and let the coroutine promise schedule work on it.
I have this example around that is not exactly simple but may do the work:
Make your coroutine return a task<T>.
task<int> async_add(int a, int b) { ... }
Let the task share a state with its coroutine_promise. The state:
is implemented as an executable, resuming the coroutine when executed, and
holds the result of the operation (e.g. a std::promise<T>).
template <typename T>
class task<T>::state : public executable {
public:
void execute() noexcept override {
handle_.resume();
}
...
private:
handle_type handle_;
std::promise<T> result_;
};
The coroutine_promise returns a task_scheduler awaiter at initial_suspend:
template <typename T>
class task<T>::coroutine_promise {
public:
auto initial_suspend() {
return task_scheduler<task<T>>{};
}
The task_scheduler awaiter schedules the state:
template <is_task task_t>
struct task_scheduler : public std::suspend_always {
void await_suspend(task_t::handle_type handle) const noexcept {
thread_pool::get_instance().schedule(handle.promise().get_state());
}
};
Wrapping it all up: calls to a coroutine will make a state be scheduled on a thread, and, whenever a thread executes that state, the coroutine will be resumed. The caller can then wait for the task's result.
auto c{ async_add(a,b) };
b = c.get_result();
[Demo]
That example is from 2018, and was built for the Coroutine TS. So it's missing a lot of stuff from the actual C++20 feature. It also assumes the presence of a lot of things that didn't make it into C++20. The most notable of which being the idea that std::future is an awaitable type, and that it has continuation support when coupled with std::async.
It's not, and it doesn't. So there's not much you can really learn from this example.
co_await is ultimately built on the ability to suspend execution of a function and schedule its resumption after some value has been successfully computed. The actual C++20 std::future has exactly none of the machinery needed to do that. Nor does std::asyc give it the ability to do so.
As such, neither is an appropriate tool for this task.
You need to build your own future type (possibly using std::promise/future internally) which has a reference to your thread pool. When you co_await on this future, it is that new future which passes off the coroutine_handle to the thread pool, doing whatever is needed to ensure that this handle does not get executed until its current set of tasks is done.
Your pool or whatever needs to have a queue of tasks, such that it can insert new ones to be processed after all of the current one, and remove tasks once they've finished (as well as starting the next one). And those operations need to be properly synchronized. This queue needs to be accessible by both the future type and your coroutine's promise type.
When a coroutine ends, the promise needs to tell the queue that its current task is over and to move to the next one, or suspend the thread if there is no next one. And the promise's value needs to be forwarded to the next task. When a coroutine co_awaits on a future from your system, it needs to add that handle to the queue of tasks to be performed, possibly starting up the thread again.
I have a class with a function that takes a std::function and stores it. This part seems to compile ok (but please point out any issue if there are any)
#include <functional>
#include <iostream>
struct worker
{
std::function<bool(std::string)> m_callback;
void do_work(std::function<bool(std::string)> callback)
{
m_callback = std::bind(callback, std::placeholders::_1);
callback("hello world\n");
}
};
// pretty boring class - a cut down of my actual class
struct helper
{
worker the_worker;
bool work_callback(std::string str)
{
std::cout << str << std::endl;
return true;
}
};
int main()
{
helper the_helper;
//the_helper.the_worker.do_work(std::bind(&helper::work_callback, the_helper, std::placeholders::_1)); // <---- SEGFAULT (but works in minimal example)
the_helper.the_worker.do_work(std::bind(&helper::work_callback, &the_helper, std::placeholders::_1)); // <---- SEEMS TO WORK
}
I get a segfault, but I am not sure why. I have used this before, in fact, I copied this example from another place I used it. The only real difference that the member function was part of the class I called it from (i.e. this instead of the_helper).
So this is why I am also asking if there is anything else I am doing wrong in general? Like should I be passing the std::function as:
void do_work(std::function<bool(std::string)>&& callback)
or
void do_work(std::function<bool(std::string)>& callback)
As also noted by #Rakete1111 in comments, the problem probably was in this code:
bool work_callback(std::string str)
{
std::cout << str << std::endl;
}
In C++ if a non-void function does not return a value the result is undefined behavior.
This example will crash with clang but pass with gcc.
If helper::work_callback returns (e.g, true) the code works just fine.
I don't know why your code seg faults because I was spoiled and skipped std::bind straight to lambdas. Since you use C++11 you should really convert your code from std::bind to lambdas:
struct worker
{
std::function<bool(std::string)> m_callback;
void do_work(std::function<bool(std::string)> callback)
{
m_callback = callback;
callback("hello world\n");
}
};
Now with work_callback and calling do_work things need some analysis.
First version:
struct helper
{
worker the_worker;
bool work_callback(std::string)
{
return false;
}
};
int main()
{
helper the_helper;
the_helper.the_worker.do_work([&](std::string s) { return the_helper.work_callback(s); });
}
Now this version works with your toy example. However out in the wild you need to be careful. The lambda passed to do_work and then stored in the_worker captures the_helper by reference. This means that this code is valid only if the helper object passed as reference to the lambda outlives the worker object that stores the m_callback. In your example the worker object is a sub-object of the the helper class so this is true. However if in your real example this is not the case or you cannot prove this, then you need to capture by value.
First attempt to capture by value (does not compile):
struct helper
{
worker the_worker;
bool work_callback(std::string)
{
return false;
}
};
int main()
{
helper the_helper;
the_helper.the_worker.do_work([=](std::string s) { return the_helper.work_callback(s); });
}
This does not compile because the copy of the_helper stored in the lambda object is const by default and as such you cannot call work_callback on it.
A questionable solution if you can't make work_callback const is to make the lambda mutable:
struct helper
{
worker the_worker;
bool work_callback(std::string)
{
return false;
}
};
int main()
{
helper the_helper;
the_helper.the_worker.do_work([=](std::string s) mutable { return the_helper.work_callback(s); });
}
But you need to think if this is what you intended.
What would make more sense is to make work_callback const:
struct helper
{
worker the_worker;
bool work_callback(std::string) const
{
return false;
}
};
int main()
{
helper the_helper;
the_helper.the_worker.do_work([=](std::string s) { return the_helper.work_callback(s); });
}
The reason for getting SEGFAULT has been already mentioned in the comments.
However, I would like to point out that, you need to use neither std::bind nor std::function, here in your given case. Instead, simply having a lambda and a function pointer you can handle what you intend to do.
struct worker
{
typedef bool(*fPtr)(const std::string&); // define fun ptr type
fPtr m_callback;
void do_work(const std::string& str)
{
// define a lambda
m_callback = [](const std::string& str)
{
/* do something with string*/
std::cout << "Call from worker: " << str << "\n";
return true;
};
bool flag = m_callback(str);// just call the lambda here
/* do some other stuff*/
}
};
struct helper
{
worker the_worker;
bool work_callback(const std::string& str)
{
std::cout << "Call from helper: ";
this->the_worker.do_work(str);
return true; ------------------------>// remmeber to keep the promise
}
};
And use case would be:
int main()
{
helper the_helper;
the_helper.work_callback(std::string("hello world"));
// or if you intend to use
the_helper.the_worker.do_work(std::string("hello world"));
return 0;
}
see Output here:
PS: In the above case, if worker does not required m_callback for later cases(i.e, only for do_work()), then you can remove this member, as lambdas can be created and called at same place where it has been declared.
struct worker
{
void do_work(const std::string& str)
{
bool flag = [](const std::string& str)->bool
{
/* do something with string*/
std::cout << "Call from worker: " << str << "\n";
return true;
}(str); -------------------------------------> // function call
/* do other stuff */
}
};
I've read various answer on SO and still didn't understood how I should make an object method to be callable in this case:
Considering:
Class A
{
void generator(void)
{
int i = 1;
while(1)
{
if(i == 1)
{
one(/***/);//Should be a flag
i = 2;
}
else
{
two(/**/);//Should be a flag
i = 1;
}
}
}
template <typename CallbackFunction>
void one(CallbackFunction&& func)
{
}
template <typename CallbackFunction>
void two(CallbackFunction&& func)
{
}
A()
{
std::thread t(&A::generator, this);
t.detach();
}
};
and a simple main file:
void pOne(/**/)
{
std::cout<<"1"<<std::endl;
}
void pTwo(/**/)
{
std::cout<<"2"<<std::endl;
}
A myA;
A.One(pOne);
A.Two(pTwo);
int main(int argc, char** argv)
{
while(1){}
}
Here are where I'm at:
generator() should update a flag, and both one() & two() should poll on that flag & loop forever.
One() (two() also) should have a function pointer as parameters and if necessary other parameters, pOne() should have the same parameters except the function pointer.
So my questions are:
1) Is my understanding correct?
2) Is there a clean way to make generator() to start one() or two() ? (flags, semaphore, mutex, or anything that is a standard way to do it)
3) Assuming that the code was working, is it behaving as I expect ? i.e. printing 1 and 2?
if it matters, I'm on ubuntu
Disclaimer 1: Like everyone else, I'm interpreting the question as:
-> You need an event handler
-> You want callback methods on those events
And the only reason I think that is because I helped you on a i2c handler sequence before.
Also, there are better logic than this, its provided following your stubs "rules".
You mentioned that you are on Ubuntu, so you will be lacking windows event system.
Disclaimer 2:
1- To avoid going to deep I'm going to use a simple way to handle events.
2- Code is untested & provided for logic only
class Handler
{
private:
std::mutex event_one;
event_one.lock();
void stubEventGenerator(void)
{
for(;;)
{
if(!event_one.try_lock())
{
event_one.unlock();
}
sleep(15); //you had a sleep so I added one
}
}
template <typename CallbackFunction>
void One__(CallbackFunction && func)
{
while(1)
{
event_one.lock();
func();
}
}
public:
Handler()
{
std::thread H(&Handler::stubEventGenerator, this);
}
~Handler()
{
//clean threads, etc
//this should really have a quit handler
}
template <typename CallbackFunction>
void One(CallbackFunction && func) //I think you have it right, still I'm not 100% sure
{
std::thread One_thread(&Handler::One__, this, func); //same here
}
};
Some points:
One() as to be a wrapper for the thread calling One__() if you want it to be non-blocking.
mutex can be a simple way to handle events as long as the same event doesn't occur during its previous occurence (you are free to use a better/more suitable tool for your use case, or use boost:: only if necessary)
Prototype of One() & One__() are probably wrong, that's some research for you.
Finally: How it works:
std::mutex.lock() is blocking as long as it can't lock the mutex, thus One__ will wait as long as your event generator won't unlock it.
Once unlock One__ will execute your std::function & wait for the event (mutex) to be raised (unlock) again.
far from a perfect answer, but lack of time, and not being able to put that in a comment made me post it, will edit later
With whatever limited information you provided this code can be made compilable in following manner:
#include <iostream>
#include <thread>
typedef void(*fptr)();
void pOne(/**/)
{
std::cout<<"1"<<std::endl;
}
void pTwo(/**/)
{
std::cout<<"2"<<std::endl;
}
class A
{
public:
void generator(void)
{
int i = 1;
while(1)
{
if(i == 1)
{
fptr func = pOne;
one(func);//Should be a flag
i = 2;
}
else
{
fptr func = pTwo;
two(func);//Should be a flag
i = 1;
}
}
}
template <typename CallbackFunction>
void one(CallbackFunction&& func)
{
func();
}
template <typename CallbackFunction>
void two(CallbackFunction&& func)
{
func();
}
A()
{
std::thread t(&A::generator, this);
t.detach();
}
};
int main()
{
A myA;
while(1)
{
}
return 0;
}
If you want that one and two should accept any type/number of arguments then pass second argument as variadic template.Also I could not understand why you want one and two to be called from main as your generator function is for this purpose only and this generator function is called from thread which is detached in class constructor
I have designed a simple callback-keyListener-"Interface" with the help of a pure virtual function. Also I used a shared_ptr, to express the ownership and to be sure, that the listener is always available in the handler. That works like a charme, but now I want to implement the same functionality with the help of std::function, because with std::function I am able to use lambdas/functors and I do not need to derive from some "interface"-classes.
I tried to implement the std::function-variant in the second example and it seems to work, but I have two questions related to example 2:
Why does this example still work, although the listener is out of scope? (It seems, that we are working with a copy of the listener instead of the origin listener?)
How can I modify the second example, to achieve the same functionality like in the first example (working on the origin listener)? (member-ptr to std::function seems not to work! How can we handle here the case, when the listener is going out of scope before the handler? )
Example 1: With a virtual function
#include <memory>
struct KeyListenerInterface
{
virtual ~KeyListenerInterface(){}
virtual void keyPressed(int k) = 0;
};
struct KeyListenerA : public KeyListenerInterface
{
void virtual keyPressed(int k) override {}
};
struct KeyHandler
{
std::shared_ptr<KeyListenerInterface> m_sptrkeyListener;
void registerKeyListener(std::shared_ptr<KeyListenerInterface> sptrkeyListener)
{
m_sptrkeyListener = sptrkeyListener;
}
void pressKey() { m_sptrkeyListener->keyPressed(42); }
};
int main()
{
KeyHandler oKeyHandler;
{
auto sptrKeyListener = std::make_shared<KeyListenerA>();
oKeyHandler.registerKeyListener(sptrKeyListener);
}
oKeyHandler.pressKey();
}
Example 2: With std::function
#include <functional>
#include <memory>
struct KeyListenerA
{
void operator()(int k) {}
};
struct KeyHandler
{
std::function<void(int)> m_funcKeyListener;
void registerKeyListener(const std::function<void(int)> &funcKeyListener)
{
m_funcKeyListener = funcKeyListener;
}
void pressKey() { m_funcKeyListener(42); }
};
int main()
{
KeyHandler oKeyHandler;
{
KeyListenerA keyListener;
oKeyHandler.registerKeyListener(keyListener);
}
oKeyHandler.pressKey();
}
std::function<Sig> implements value semantic callbacks.
This means it copies what you put into it.
In C++, things that can be copied or moved should, well, behave a lot like the original. The thing you are copying or moving can carry with it references or pointers to an extrenal resource, and everything should work fine.
How exactly to adapt to value semantics depends on what state you want in your KeyListener; in your case, there is no state, and copies of no state are all the same.
I'll assume we want to care about the state it stores:
struct KeyListenerA {
int* last_pressed = 0;
void operator()(int k) {if (last_pressed) *last_pressed = k;}
};
struct KeyHandler {
std::function<void(int)> m_funcKeyListener;
void registerKeyListener(std::function<void(int)> funcKeyListener) {
m_funcKeyListener = std::move(funcKeyListener);
}
void pressKey() { m_funcKeyListener(42); }
};
int main() {
KeyHandler oKeyHandler;
int last_pressed = -1;
{
KeyListenerA keyListener{&last_pressed};
oKeyHandler.registerKeyListener(keyListener);
}
oKeyHandler.pressKey();
std::cout << last_pressed << "\n"; // prints 42
}
or
{
oKeyHandler.registerKeyListener([&last_pressed](int k){last_pressed=k;});
}
here we store a reference or pointer to the state in the callable. This gets copied around, and when invoked the right action occurs.
The problem I have with listeners is the doulbe lifetime issue; a listener link is only valid as long as both the broadcaster and reciever exist.
To this end, I use something like this:
using token = std::shared_ptr<void>;
template<class...Message>
struct broadcaster {
using reciever = std::function< void(Message...) >;
token attach( reciever r ) {
return attach(std::make_shared<reciever>(std::move(r)));
}
token attach( std::shared_ptr<reciever> r ) {
auto l = lock();
targets.push_back(r);
return r;
}
void operator()( Message... msg ) {
decltype(targets) tmp;
{
// do a pass that filters out expired targets,
// so we don't leave zombie targets around forever.
auto l = lock();
targets.erase(
std::remove_if( begin(targets), end(targets),
[](auto&& ptr){ return ptr.expired(); }
),
end(targets)
);
tmp = targets; // copy the targets to a local array
}
for (auto&& wpf:tmp) {
auto spf = wpf.lock();
// If in another thread, someone makes the token invalid
// while it still exists, we can do an invalid call here:
if (spf) (*spf)(msg...);
// (There is no safe way around this issue; to fix it, you
// have to either restrict which threads invalidation occurs
// in, or use the shared_ptr `attach` and ensure that final
// destruction doesn't occur until shared ptr is actually
// destroyed. Aliasing constructor may help here.)
}
}
private:
std::mutex m;
auto lock() { return std::unique_lock<std::mutex>(m); }
std::vector< std::weak_ptr<reciever> > targets;
};
which converts your code to:
struct KeyHandler {
broadcaster<int> KeyPressed;
};
int main() {
KeyHandler oKeyHandler;
int last_pressed = -1;
token listen;
{
listen = oKeyHandler.KeyPressed.attach([&last_pressed](int k){last_pressed=k;});
}
oKeyHandler.KeyPressed(42);
std::cout << last_pressed << "\n"; // prints 42
listen = {}; // detach
oKeyHandler.KeyPressed(13);
std::cout << last_pressed << "\n"; // still prints 42
}