Programowanie w języku C/C++ » Artykuły

Events


Strona w budowie
Ktoś pracuje nad tą stroną, jej zawartość może się wkrótce zmienić. Prosimy o cierpliwość!


Byłbym bardzo wdziędzny o nie-edytowanie tego artykułu, tylko zgłaszanie uwag w wątku: http://forum.4programmers.net/viewtopic.php?id=121925

Natchnięty wiosennym promykiem słońca, postanowiłem sprawdzić, czy dało by się uzyskać coś na kształt eventów  i eventhandlerów (wykraczających poza funkcje niezwiązane i metody statyczne, przyjmujacych metody od roznych klas, z parametrami lub bez)

Ponizej aktualna postac mojego radosnego wymyslu. Obsluguje eventhandlery z klas zwyklych i polimorficznych, oraz do trzech argumentow eventa.

Wybaczcie forme.. jesli nie wykryje w tym zadnego razacego uchybienia, wrzuce to na jakies repozytorium pozniej.

Najprostszy przyklad uzycia to plik basic_test.cpp. Wydaje mi sie w miare intuicyjny. Jedyna uwaga: cyferka w konkretyzacji szablonu oznacza ilosc argumentow eveta. Od strony uzytkowej, wszystko sprowadza sie do uzywania klasy Event oraz funkcji makehandler.

W przykladzie (bedacym jednoczenie testem:) ) klasa RAT definiuje dwa Eventy, jeden bez, drugi z prostym parametrem. Klasa Observer zawiera kilka metod, ktorych mozna uzyc jako handlerow reagujacych na wystapienie (Event::fire()) zdarzenia. W funkcji basic_test tworzone sa przykladowe obiekty i pokazane jest jak spinac (i odpinac) handlery do(od) eventow.

Klasa ktorej metody mialy by byc uzyte jako handlery, musi miec dolaczonego mix-in'a MEventHandlerOwner
..innymi slowy, musi dziedziczyc (publicznie!) po MEventHandlerOwner<nazwa_mojej_klasy>.
Wymog ten trudno mi jest w tej chwili wytlumaczyc.. dosc powiedziec, ze ze wzgledu na problemy z funkcjami virtualnymi (zwlaszcza pure virtual, patrz virtual_test) oraz skladowanem i rzutowaniem wskaznikow-na-te-metody, okazalo sie konieczne, aby wszystkie klasy dostarczajace handlerow posiadaly vtable i znajdowaly sie w tym samym drzewie klas.

PS. kod sprawdzany pod VisualStudio2005 i g++ version 3.3.6. Na g++ jest czysto w trybach -Wall i -ansi, niestety na -pedantic nie mialem juz nerwow :)


MEventHandlerOwner.h
#ifndef _MEVENTHANDLEROWNER_H
#define _MEVENTHANDLEROWNER_H
 
class MEventHandlerOwnerBase
{public:
        virtual ~MEventHandlerOwnerBase(){}             //ensure all MEHOwners have a vtable
};
 
template<typename T>
class MEventHandlerOwner : public MEventHandlerOwnerBase
{
public:
        template<typename T2>
        void invoke(void (MEventHandlerOwnerBase::*const member)()){(dynamic_cast<T2*>(static_cast<T*>(this))->*member)();}
 
        template<typename T2, typename A1>
        void invoke(void (MEventHandlerOwnerBase::*const member)(A1), A1 a1){(dynamic_cast<T2*>(static_cast<T*>(this))->*member)(a1);}
 
        template<typename T2, typename A1, typename A2>
        void invoke(void (MEventHandlerOwnerBase::*const member)(A1, A2), A1 a1, A2 a2){(dynamic_cast<T2*>(static_cast<T*>(this))->*member)(a1, a2);}
 
        template<typename T2, typename A1, typename A2, typename A3>
        void invoke(void (MEventHandlerOwnerBase::*const member)(A1, A2, A3), A1 a1, A2 a2, A3 a3){(dynamic_cast<T2*>(static_cast<T*>(this))->*member)(a1, a2, a3);}
};
 
#endif //_MEVENTHANDLEROWNER_H


EventHandlerBase.h
#ifndef _EVENTHANDLERBASE_H
#define _EVENTHANDLERBASE_H
 
#include "MEventHandlerOwner.h"
 
template<int N, typename A1 = void, typename A2 = void, typename A3 = void, typename LAST_AND_UNUSED = void>
class EventHandlerBase;
 
template<>
class EventHandlerBase<0>
{
protected:
        MEventHandlerOwnerBase* const target;
        void (MEventHandlerOwnerBase::* const method)();
public:
        EventHandlerBase(MEventHandlerOwnerBase* const object, void (MEventHandlerOwnerBase::* const member)()):target(object),method(member){}
    virtual void invoke()=0;
};
 
template<typename A1>
class EventHandlerBase<1,A1>
{
protected:
        MEventHandlerOwnerBase* const target;
        void (MEventHandlerOwnerBase::* const method)(A1);
public:
        EventHandlerBase(MEventHandlerOwnerBase* const object, void (MEventHandlerOwnerBase::* const member)(A1)):target(object),method(member){}
    virtual void invoke(A1 a1)=0;
};
 
template<typename A1, typename A2>
class EventHandlerBase<2,A1,A2>
{
protected:
        MEventHandlerOwnerBase* const target;
        void (MEventHandlerOwnerBase::* const method)(A1, A2);
public:
        EventHandlerBase(MEventHandlerOwnerBase* const object, void (MEventHandlerOwnerBase::* const member)(A1, A2)):target(object),method(member){}
    virtual void invoke(A1 a1, A2 a2)=0;
};
 
template<typename A1, typename A2, typename A3>
class EventHandlerBase<3,A1,A2,A3>
{
protected:
        MEventHandlerOwnerBase* const target;
        void (MEventHandlerOwnerBase::* const method)(A1, A2, A3);
public:
        EventHandlerBase(MEventHandlerOwnerBase* const object, void (MEventHandlerOwnerBase::* const member)(A1, A2, A3)):target(object),method(member){}
    virtual void invoke(A1 a1, A2 a2, A3 a3)=0;
};
 
#endif //_EVENTHANDLERBASE_H


EventHandler.h
#ifndef _EVENTHANDLER_H
#define _EVENTHANDLER_H
 
#include "EventHandlerBase.h"
 
template<typename T, typename A1 = void, typename A2 = void, typename A3 = void, typename LAST_AND_UNUSED = void>
struct EventHandler;
 
 
template<typename T>
struct EventHandler<T> : EventHandlerBase<0>
{   EventHandler(T* const object, void(MEventHandlerOwnerBase::* const member)() ):EventHandlerBase<0>(object,member){}
        void invoke(){static_cast<T*>(target)->template invoke<T>(method);}
};
 
template<typename T>
boost::shared_ptr<EventHandlerBase<0> > make_handler(T& object, void (T::* const member)() )
{   return boost::shared_ptr<EventHandlerBase<0> >(new EventHandler<T>(&object, static_cast<void(MEventHandlerOwnerBase::*const)()>(member)));
}
 
 
template<typename T, typename A1>
struct EventHandler<T, A1> : EventHandlerBase<1, A1>
{   EventHandler(T* const object, void(MEventHandlerOwnerBase::* const member)(A1) ):EventHandlerBase<1, A1>(object,member){}
        void invoke(A1 a1){static_cast<T*>(target)->template invoke<T, A1>(method, a1);}
};
 
template<typename T, typename A1>
boost::shared_ptr<EventHandlerBase<1, A1> > make_handler(T& object, void (T::* const member)(A1) )
{   return boost::shared_ptr<EventHandlerBase<1, A1> >(new EventHandler<T, A1>(&object, static_cast<void(MEventHandlerOwnerBase::*const)(A1)>(member)));
}
 
 
template<typename T, typename A1, typename A2>
struct EventHandler<T, A1, A2> : EventHandlerBase<2, A1, A2>
{   EventHandler(T* const object, void(MEventHandlerOwnerBase::* const member)(A1, A2) ):EventHandlerBase<2, A1, A2>(object,member){}
        void invoke(A1 a1, A2 a2){static_cast<T*>(target)->template invoke<T, A1, A2>(method, a1, a2);}
};
 
template<typename T, typename A1, typename A2>
boost::shared_ptr<EventHandlerBase<2, A1, A2> > make_handler(T& object, void (T::* const member)(A1, A2) )
{   return boost::shared_ptr<EventHandlerBase<2, A1, A2> >(new EventHandler<T, A1, A2>(&object, static_cast<void(MEventHandlerOwnerBase::*const)(A1, A2)>(member)));
}
 
 
template<typename T, typename A1, typename A2, typename A3>
struct EventHandler<T, A1, A2, A3> : EventHandlerBase<3, A1, A2, A3>
{   EventHandler(T* const object, void(MEventHandlerOwnerBase::* const member)(A1, A2, A3) ):EventHandlerBase<3, A1, A2, A3>(object,member){}
        void invoke(A1 a1, A2 a2, A3 a3){static_cast<T*>(target)->template invoke<T, A1, A2, A3>(method, a1, a2, a3);}
};
 
template<typename T, typename A1, typename A2, typename A3>
boost::shared_ptr<EventHandlerBase<3, A1, A2, A3> > make_handler(T& object, void (T::* const member)(A1, A2, A3) )
{   return boost::shared_ptr<EventHandlerBase<3, A1, A2, A3> >(new EventHandler<T, A1, A2, A3>(&object, static_cast<void(MEventHandlerOwnerBase::*const)(A1, A2, A3)>(member)));
}
 
 
#endif //_EVENTHANDLER_H


EventBase.h
#ifndef _EVENTBASE_H
#define _EVENTBASE_H
 
#include <list>
#include <boost/smart_ptr.hpp>
 
#include "EventHandler.h"
 
template<int N, typename A1 = void, typename A2 = void, typename A3 = void>
struct EventBase : protected std::list<boost::shared_ptr<EventHandlerBase<N, A1, A2, A3> > >
{
public:
        typedef EventHandlerBase<N, A1, A2, A3> element;
        typedef boost::shared_ptr<element> safepointer;
        typedef std::list<safepointer> innerlist;
 
        using innerlist::push_back;
        using innerlist::push_front;
        using innerlist::remove;
 
        safepointer pop_back()
        {       safepointer tmp = back();
                innerlist::pop_back();
                return tmp;
        }
        safepointer pop_front()
        {       safepointer tmp = front();
                innerlist::pop_front();
                return tmp;
        }
};
 
#endif //_EVENTBASE_H


Event.h
#ifndef _EVENT_H
#define _EVENT_H
 
#include "EventBase.h"
 
template<int N, typename A1 = void, typename A2 = void, typename A3 = void, typename LAST_AND_UNUSED = void>
struct Event;
 
template<>
struct Event<0> : EventBase<0>
{
        void fire() const
        {       innerlist::const_iterator _it = begin(), _end = end();
                while(_it!=_end)
                        (*_it++)->invoke();
        }
};
 
template<typename A1>
struct Event<1, A1> : EventBase<1, A1>
{
        void fire(A1 a1) const
        {   typename innerlist::const_iterator _it = begin(), _end = end();
                while(_it!=_end)
                        (*_it++)->invoke(a1);
        }
};
 
template<typename A1, typename A2>
struct Event<2, A1, A2> : EventBase<2, A1, A2>
{
        void fire(A1 a1, A2 a2) const
        {       typename innerlist::const_iterator _it = begin(), _end = end();
                while(_it!=_end)
                        (*_it++)->invoke(a1, a2);
        }
};
 
template<typename A1, typename A2, typename A3>
struct Event<3, A1, A2, A3> : EventBase<3, A1, A2, A3>
{
        void fire(A1 a1, A2 a2, A3 a3) const
        {       typename innerlist::const_iterator _it = begin(), _end = end();
                while(_it!=_end)
                        (*_it++)->invoke(a1, a2, a3);
        }
};
 
#endif //_EVENT_H


main.cpp
#include <iostream>
#include <string>
 
#include "tests.h"
 
int main()
{       basic_test();
        virtual_test();
        copyargs_test();
 
#ifdef VERBOSE
        std::cin.get();
#endif
}


tests.h
#ifndef _TESTS_H
#define _TESTS_H
 
#define VERBOSE
 
void basic_test();
void virtual_test();
void copyargs_test();
 
#endif  //_TESTS_H


basic_test.cpp
#include <string>
 
#include "Event.h"
 
#include "tests.h"
#ifdef VERBOSE
#include <iostream>
#endif
 
class Rat
{
public:
        Event<1, std::string const&> before_method;
        Event<0> after_method;
 
        void method()
        {       std::string nonemptystring = "rat";
                before_method.fire(nonemptystring);
#ifdef VERBOSE
                std::cout << "Rat squeaks" << std::endl;
#endif
                after_method.fire();
        }
};
 
class Observer : public MEventHandlerOwner<Observer>
{
    std::string name;
public:
        Observer(char const* str):name(str),sit_called(false),jump_called(false),param_passed(false){}
        void sit(std::string const & onwhat)
        {
#ifdef VERBOSE
                std::cout << name << " sits on " << onwhat << std::endl;
#endif
                param_passed = !onwhat.empty();
                sit_called = true;
        }
    void jump()
        {
#ifdef VERBOSE
                std::cout << name << " screams" << std::endl;
#endif
                jump_called=true;
        }
        bool sit_called;
        bool jump_called;
        bool param_passed;
};
 
 
void basic_test()
{
        Rat rat;
        Observer woman("Jane");
 
        //non-virtual member function test
        rat.before_method.push_back(make_handler(woman, &Observer::sit));
        rat.after_method.push_back(make_handler(woman, &Observer::jump));
    rat.method();
 
        assert(woman.sit_called);
        assert(woman.jump_called);
        assert(woman.param_passed);
}


virtual_test.cpp
#include <string>
 
#include "Event.h"
 
#include "tests.h"
#ifdef VERBOSE
#include <iostream>
#endif
 
class Rat
{
public:
        Event<1, std::string const&> before_method;
        Event<0> after_method;
 
        void method()
        {       std::string nonemptystring = "rat";
                before_method.fire(nonemptystring);
#ifdef VERBOSE
                std::cout << "Rat squeaks" << std::endl;
#endif
                after_method.fire();
        }
};
 
class Observer_base : public MEventHandlerOwner<Observer_base>
{
protected:
    std::string name;
public:
    Observer_base(char const* str):name(str),sit_called(false),jump_called(false),param_passed(false){}
    virtual void sit(std::string const & onwhat)=0;
    virtual void jump()=0;
        bool sit_called;
        bool jump_called;
        bool param_passed;
};
 
class Observer_derived : public Observer_base
{
public:
    Observer_derived(char const* str):Observer_base(str){}
        void sit(std::string const & onwhat)
        {
#ifdef VERBOSE
                std::cout << name << " sits on " << onwhat << std::endl;
#endif
                param_passed = !onwhat.empty();
                sit_called = true;
        }
    void jump()
        {
#ifdef VERBOSE
                std::cout << name << " screams" << std::endl;
#endif
                jump_called=true;
        }
};
 
 
void virtual_test()
{
        Rat rat;
        Observer_derived woman("Mary");
 
        //virtual member function test
        rat.before_method.push_back(make_handler(woman, &Observer_derived::sit));
        rat.after_method.push_back(make_handler(woman, &Observer_derived::jump));
    rat.method();
        rat.before_method.pop_back();
        rat.after_method.pop_back();
 
        assert(woman.sit_called);
        assert(woman.jump_called);
        assert(woman.param_passed);
}


copyargs_test.cpp
#include "Event.h"
 
#include "tests.h"
 
#ifdef VERBOSE
#include <iostream>
#endif
 
struct CopyWatch
{
        static unsigned long copycount;
        CopyWatch()
        {
#ifdef VERBOSE
                std::cout << "Created" << std::endl;
#endif
        }
        CopyWatch(CopyWatch const&)
        {       ++copycount;
#ifdef VERBOSE
                std::cout << "Copied" << std::endl;
#endif
        }
};
 
class CopyWatchActor
{public:
        Event<3, CopyWatch const&, CopyWatch const&, CopyWatch const&> copy_test_event;
 
        void copy_test()
        {       CopyWatch arg;
                copy_test_event.fire(arg, arg, arg);
        }
};
 
class CopyWatchListener : public MEventHandlerOwner<CopyWatchListener>
{public:
        void test(CopyWatch const&, CopyWatch const&, CopyWatch const&){};
};
 
unsigned long CopyWatch::copycount = 0;
 
void copyargs_test()
{
        //are the const-ref arguments passed by reference correctly?
        CopyWatchActor a;
        CopyWatchListener x;
        a.copy_test_event.push_back(make_handler(x, &CopyWatchListener::test));
        a.copy_test();
 
        assert( CopyWatch::copycount == 0);
}