Reputation: 497
I have done a bit of research on the matter but have not come to a concrete solution. I would really like to be able to do this:
public delegate void VoidFloatCallback(float elapsedTime);
public VoidFloatCallback OnEveryUpdate;
public VoidFloatCallback OnNextUpdate;
public virtual void Update(GameTime gameTime)
{
if (OnNextUpdate != null)
{
OnNextUpdate(gameTime);
OnNextUpdate = null;
}
if (OnEveryUpdate != null)
{
OnEveryUpdate(gameTime);
}
this.OnUpdate(gameTime);
}
But in C++ of course. I have found only one solution that provides me with such a feature; but has since been taken offline but I reposted it here http://codepad.org/WIVvFHv0. The only issue with the solution I have found is that it isn't modern C++11 code and lacks lambda support.
I know that I can use
std::function
but the only issue with that is it does not support the operators "+=
, -=
, ==
". Now I have thought about making my own Events class and having a
vector<std::function>
with some templating but I found out that std::function does not implement the operator ==
so I couldn't make it look and feel like C# does.
Anyway, my question is this:
I would to know how I can implement such an event system using C++11 -- or if it is even possible. Or even if you know of a better/proper way to implement Callbacks that support multiple listeners (I'd like to avoid a full blown Observer Pattern implementation if at all possible.)
Update #1
My intentions for the operators were this:
void some_func(float f) { /** do something with f **/ }
void some_other_func(float f) { /** do something else with f **/ }
OnNextUpdate += some_func();
OnNextUpdate += some_other_func();
OnNextUpdate(5.0f);
// both some_func() and some_other_func() are called
OnNextUpdate -= some_other_func();
OnNextUpdate(5.0f);
// only some_func() is called
Upvotes: 9
Views: 9882
Reputation: 9
I've been using C# for quite a while, but I'm still kinda new to C++ so take my answer with a grain of salt.
You can achieve something similar with a very simple implementation
#include <algorithm>
#include <vector>
template <typename T, typename... Args>
class Delegate final {
private:
using Invokable = T(*)(Args...);
std::vector<Invokable> _functions;
public:
void operator+=(const Invokable func) {
_functions.push_back(func);
}
void operator-=(const Invokable func) {
auto it{ std::remove_if(_functions.begin(), _functions.end(),
[&func](const Invokable& f) { return f == func; })};
_functions.erase(it, _functions.end());
}
void operator()(Args&&... args) const {
for (const Invokable& func : _functions) {
(*func)(std::forward<Args>(args)...);
}
}
};
Then you can add or remove as many functions as you want, as long as they match the delegate's signature. Here's some sample code.
#include <iostream>
void Foo(int val) { std::cout << "Foo function called with " << val << "\n"; }
void Bar(int val) { std::cout << "Bar function called with " << val << "\n"; }
int main() {
Delegate<void, int> delegate;
delegate += Foo;
delegate += Bar;
delegate(10);
delegate -= Foo;
delegate(20);
return 0;
}
It will print
Foo function called with 10
Bar function called with 10
Bar function called with 20
Beware that my code is very barebone; you can extend this class to add more functionality.
Upvotes: 0
Reputation: 727
There are numerous libraries out there providing this kind of thing. Some call operator+= for delegates something like "connect" or "subscribe". Examples are boost.signal2, poco AbstractEvent, libsigc++ or if you are using a GUI Qt's slot/signal (or if you are using gtk in c++, c++-gtk-utils Emitters).
Upvotes: 1
Reputation: 343
What about boost.signals2?
boost::signals2::signal<void (float)> onEveryUpdate;
boost::signals2::signal<void (float)> onNextUpdate;
virtual void Update(float gameTime)
{
onNextUpdate(gameTime);
onNextUpdate.disconnect_all_slots();
onEveryUpdate(gameTime);
}
The signal's connect
function is basically what you mean by +=
.
Upvotes: 2
Reputation: 12016
The Poco Library has support for delegates like this for example:
#include <iostream>
#include "Poco/Delegate.h"
#include "Poco/BasicEvent.h"
using namespace Poco;
struct Source {
BasicEvent<int> theEvent;
void fireEvent(int n) { theEvent(this, n); }
};
struct Target1 {
void onEvent(const void * /*sender*/, int &arg) {
std::cout << "onEvent from Target1: " << arg << std::endl;
}
};
struct Target2 {
void onEvent(const void * /*sender*/, int &arg) {
std::cout << "onEvent from Target2: " << arg << std::endl;
}
};
int main() {
Source source;
Target1 target1;
Target2 target2;
source.theEvent += delegate(&target1, &Target1::onEvent);
source.theEvent += delegate(&target2, &Target2::onEvent);
source.fireEvent(42);
source.theEvent -= delegate(&target2, &Target2::onEvent);
source.fireEvent(24);
return 0;
}
Output:
onEvent from Target1: 42
onEvent from Target2: 42
onEvent from Target1: 24
Library wise, I would also recommend taking a look at Boost.Signals2.
Upvotes: 0
Reputation: 153955
The C++ function object mechanism is quite different from the C# approach. In particular, function objects are based on values rather than on references. The reason function objects can be identified when removing them in C++ is that the function object have an identity, i.e., the object they are called on and the member function being called. Also, in C++ it isn't possible to directly take the address of an object and a member function at once.
To make a system of delegates work which allows removal of functions, you could create something similar to std::function<Signature>
but using multiple functions and requiring that each of the used functions is EqualityComparable
. Below is a simple implementation of such a delegate system together with an example implementation how a binder for member functions could look like. There are many obvious extension opportunities as this implementation is only intended as a demo.
#include <algorithm>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
template <typename Signature>
struct delegate;
template <typename... Args>
struct delegate<void(Args...)>
{
struct base {
virtual ~base() {}
virtual bool do_cmp(base* other) = 0;
virtual void do_call(Args... args) = 0;
};
template <typename T>
struct call: base {
T d_callback;
template <typename S>
call(S&& callback): d_callback(std::forward<S>(callback)) {}
bool do_cmp(base* other) {
call<T>* tmp = dynamic_cast<call<T>*>(other);
return tmp && this->d_callback == tmp->d_callback;
}
void do_call(Args... args) {
return this->d_callback(std::forward<Args>(args)...);
}
};
std::vector<std::unique_ptr<base>> d_callbacks;
delegate(delegate const&) = delete;
void operator=(delegate const&) = delete;
public:
delegate() {}
template <typename T>
delegate& operator+= (T&& callback) {
this->d_callbacks.emplace_back(new call<T>(std::forward<T>(callback)));
return *this;
}
template <typename T>
delegate& operator-= (T&& callback) {
call<T> tmp(std::forward<T>(callback));
auto it = std::remove_if(this->d_callbacks.begin(),
this->d_callbacks.end(),
[&](std::unique_ptr<base>& other) {
return tmp.do_cmp(other.get());
});
this->d_callbacks.erase(it, this->d_callbacks.end());
return *this;
}
void operator()(Args... args) {
for (auto& callback: this->d_callbacks) {
callback->do_call(args...);
}
}
};
// ----------------------------------------------------------------------------
template <typename RC, typename Class, typename... Args>
class member_call {
Class* d_object;
RC (Class::*d_member)(Args...);
public:
member_call(Class* object, RC (Class::*member)(Args...))
: d_object(object)
, d_member(member) {
}
RC operator()(Args... args) {
return (this->d_object->*this->d_member)(std::forward<Args>(args)...);
}
bool operator== (member_call const& other) const {
return this->d_object == other.d_object
&& this->d_member == other.d_member;
}
bool operator!= (member_call const& other) const {
return !(*this == other);
}
};
template <typename RC, typename Class, typename... Args>
member_call<RC, Class, Args...> mem_call(Class& object,
RC (Class::*member)(Args...)) {
return member_call<RC, Class, Args...>(&object, member);
}
// ----------------------------------------------------------------------------
void f(char const* str) { std::cout << "f(" << str << ")\n"; }
void g(char const* str) { std::cout << "g(" << str << ")\n"; }
void h(char const* str) { std::cout << "h(" << str << ")\n"; }
// ----------------------------------------------------------------------------
struct foo
{
int d_id;
explicit foo(int id): d_id(id) {}
void bar(char const* str) {
std::cout << "foo(" << this->d_id << ")::bar(" << str << ")\n";
}
void cbs(char const* str) {
std::cout << "foo(" << this->d_id << ")::cbs(" << str << ")\n";
}
};
// ----------------------------------------------------------------------------
int main()
{
delegate<void(char const*)> d0;
foo f0(0);
foo f1(1);
d0 += f;
d0 += g;
d0 += g;
d0 += h;
d0 += mem_call(f0, &foo::bar);
d0 += mem_call(f0, &foo::cbs);
d0 += mem_call(f1, &foo::bar);
d0 += mem_call(f1, &foo::cbs);
d0("first call");
d0 -= g;
d0 -= mem_call(f0, &foo::cbs);
d0 -= mem_call(f1, &foo::bar);
d0("second call");
}
Upvotes: 21
Reputation: 5892
What about using the observer pattern instead?
class IVoidFloatCallback
{
public:
virtual ~IVoidFloatCallback() { }
virtual void VoidFloatCallback(float elapsedTime) = 0;
};
class Game
{
public:
std::vector<IVoidFloatCallback*> mOnEveryUpdate;
std::vector<IVoidFloatCallback*> mOnNextUpdate;
void Update(float gameTime)
{
for ( auto& update : mOnNextUpdate )
{
update->VoidFloatCallback(gameTime);
}
mOnNextUpdate.clear();
for ( auto& update : mOnEveryUpdate )
{
update->VoidFloatCallback(gameTime);
}
OnUpdate(gameTime);
}
};
class UpdateMe : public IVoidFloatCallback
{
public:
virtual void VoidFloatCallback(float elapsedTime) final
{
// Do something
}
};
void InitGame()
{
Game g;
UpdateMe someThing;
g.mOnEveryUpdate.push_back(&someThing);
g.Update(1.0f);
}
I think trying to make C++ look like C# isn't really the "thing" to do since it is pretty different. I'd take a look at the linked question about multicast too.
Upvotes: 3