C # -Like Delegates in C ++

I worked a little on this issue, but did not find a specific 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 found only one solution that provides me with such a function; but it has been disabled since then, but I posted it here http://codepad.org/WIVvFHv0 . The only problem with the solution I found is that it is not modern C ++ 11 code and does not need lambda support.

I know I can use

  std::function 

but the only problem with this is that it does not support the " += , -= , == " operators. Now I was thinking of creating my own Events class and

  vector<std::function> 

with some templates, but I found out that std :: function does not implement the == operator, so I could not make it look and feel like C #.

Anyway, my question is this:

I would like to know how I can implement such an event system using C ++ 11, or if possible. Or even if you know the best / right way to implement callbacks that support multiple listeners (I would like to avoid a full-blown implementation of the Observer pattern, if at all possible.)

Update # 1

My intentions for the operators were as follows:

  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 
+7
c ++ c # c ++ 11
source share
5 answers

The mechanism of C ++ function objects is very different from the C # approach. In particular, functional objects are value-based, not reference-based. Objects of the reason function can be identified when they are deleted in C ++, since the function object has an identifier, that is, the object on which they are called, and the called member function. In addition, in C ++ it is impossible to immediately access the address of an object and a member function.

To do delegate work that allows you to remove functions, you can create something similar to std::function<Signature> , but using several functions and requiring that each of the functions used be EqualityComparable . The following is a simple implementation of such a delegate system, as well as an example of how a link for member functions might look. There are many obvious extension possibilities since this implementation is for demonstration purposes only.

 #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"); } 
+16
source share

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 connect signal function basically means what you mean by += .

+2
source share

There are many libraries that provide such things. Some + = call statement for delegates is something like "connect" or "subscribe". Examples are boost.signal2 , poco AbstractEvent , libsig ++ or if you use the Qt slot / signal graphical interface (or if you use gtk in C ++, ++ - gtk-utils Emitters ).

+1
source share

How to use an 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 the attempt to make C ++ is similar to C #, in fact this is not a “thing” because it is completely different. I will also consider the related issue of multicast.

0
source share

The Poco Library supports delegates such as 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

The library is wise, I would also recommend taking a look at Boost.Signals2.

0
source share

All Articles