I'm trying to create a c++ event system similar to c# system. I need to be able to store any kind of functions and call them with the right parameters at the appropriate time.
The closest I can get is with std::function, bind and placeholders but here is my problem.
void Func()
{
std::cout << "Notified" << std::endl;
}
void FuncWithParam(const std::string& str)
{
std::cout << str << std::endl;
}
std::function<void()> fn = std::bind(Func); // this works
std::function<void()> fn = std::bind(FuncWithParam, "Hello there"); // this works also
std::function<void()> fn = std::bind(FuncWithParam, _1); // but this doesn't
Is it actually possible to store any kind of signature in a single std::function ? Or do I have to recourse to a more complex solution.
Here is a simple C++ broadcaster:
using token = std::shared_ptr<void>;
template<class...Ts>
struct broadcaster {
using listen = std::function<void(Ts...)>;
using sp_listen = std::shared_ptr<listen>;
using wp_listen = std::weak_ptr<listen>;
token attach( listen l ) {
return attach( std::make_shared<listen>(std::move(l)) );
}
token attach( sp_listen sp ) {
listeners.push_back(sp);
return sp;
}
void operator()(Ts...ts)const {
listeners.erase(
std::remove_if( begin(listeners), end(listeners),
[](auto&& wp){return !(bool)wp.lock();}
),
end(listeners)
);
auto tmp = listeners;
for (auto&& l : tmp) {
if (auto pf = l.lock()) {
(*pf)(ts...);
}
}
}
private:
mutable std::vector<wp_listen> listeners;
};
To listen to it, you .attach
and pass it a function to call. attach
returns a token
, and the function is invoked so long as that token (or copies of it) continue to exist.
To invoke the message, you invoke ()
on the broadcaster
.
Memory of dead callbacks is reclaimed the next time you invoke the broadcaster; indirectly owned resources are cleaned up faster.
If you register a listener while it is currently broadcasting, it won't get the current broadcast.
You can add a std::mutex
to make this usable from multiple threads at once, or externally synchronize. If you synchronize internally, I wouldn't hold the mutex as you run the for(auto&&
loop in ()
to avoid reentrancy problems.
Example use:
struct location {
int x, y;
};
struct button {
broadcaster< location > mouse_click;
broadcaster<> mouse_enter;
broadcaster<> mouse_leave;
};
struct dancer {
std::vector<token> listen_tokens;
dancer( button& b ) {
listen_tokens.push_back( b.mouse_enter.attach([this]{ dance(); } ) );
listen_tokens.push_back( b.mouse_leave.attach([this]{ end_dance(); } ) );
listen_tokens.push_back( b.mouse_click.attach(
[this](location l){
pose(l.x, l.y);
}
) );
}
void dance() const {
std::cout << "start dancing\n";
}
void pose( int x, int y ) const {
std::cout << "struck a pose at " << x << ", " << y << "\n";
}
void end_dance() const {
std::cout << "end dancing\n";
}
};
Note that no virtual
methods where used. The only polymorphism was type erasure based on std::function
.
Listening objects have to track the lifetime they which to listen over explicitly (by keeping a token
alive), and if they want to be able to unregister to a specific broadcaster they have to keep that association themselves.
If broadcasters go away first, there is no problem. If listeners go away, so long as their token preceeds them everything is ok. Attaching does cause a dynamic allocation to store the token (and a moved into copy of the listener), but only one.
This is a different approach than you'd use in C#, both because it relies on RAII and because it is not OO at its core, yet remains polymorphic.