Newsgroup: it.comp.lang.c++
Titolo: Eventi e handler di eventi in C++ standard

Salve a tutti.
Sto cercando un modo per emulare il meccanismo degli eventi usando codice
standard, per fornire alle mie classi un modo per segnalare i cambiamenti di
stato ad altre classi.
Esempio:
class Miaclasse{
    ...
    public:
        void una_funzione_qualunque(int i){
            ...
            // Qui vorrei poter chiamare un metodo di una classe _non
specificata a priori_
            // che funga da handler per l'evento, in modo che quando si ha
per Miaclasse un qualche
            // cambiamento di stato, ciò sia comunicato all'altra classe
attraverso il suo metodo
            // che sto chiamando da dentro Miaclasse.
    }
}
Poniamo per esempio che la funzione membro che funge da handler per l'evento
prenda un intero e non restituisca nulla. Vorrei poter creare una classe che
usi al suo interno un'istanza di Miaclasse, la quale richiami la suddetta
funzione ogni qualvolta ci sia da segnalare un cambiamento di stato:
class Catcher{
    // Nei costruttori indico a Miaclasse quali funzioni membro di questa
classe dovranno
    // essere richiamati per segnalare i cambiamenti di stato
    ...
    public:
        void handler(int i){
            // Qui gestisco l'evento, eseguendo tutte le azioni opportune.
Si noti che, essendo
            // handler una funzione membro di Catcher non statica, qui posso
avere accesso
            // implicitamente alla particolare istanza di Catcher che sto
utilizzando.
        }
}
Mi sono scontrato con parecchi problemi, ovvero: sto scoprendo che si
possono adottare svariate tecniche per gestire queste situazioni, ma nessuna
di queste è veramente "pulita" , nel senso che richiede sempre un po' di
attenzione nello sviluppare sia la classe che solleva l'evento che le
eventuali classi che lo dovranno eventualmente gestire, e non è possibile
risolvere la questione usando in qualche modo una "interfaccia unica e
generale".
In soldoni: qualcuno è a conoscenza di un metodo non troppo incasinato per
simulare il meccanismo degli eventi, non avendo purtroppo il C++ standard a
disposizione la soluzione ideale, e cioè l'uso dei tipi "closure" come
borland insegna ?
Grazie per gli eventuali chiarimenti!

 cambiamenti di stato ad altre classi.
Il caro vecchio pattern Observer dovrebbe andare bene, mi pare.
 
http://page.inf.fu-berlin.de/~bokowski/Observer.html
Max

 Max

 cambiamenti di stato ad altre classi.
ci ho giochicchiato tempo fa, mi e' venuta fuori una minilibreria di 
questo tipo
// message_.h
#ifndef MESSAGE__H
#define MESSAGE__H
// virtual base class for message types
class message_ {
public:
        message_(){}
        virtual ~message_(){}
};
#endif
//--------------------------------------//
// handler_.h
#ifndef HANDLER__H
#define HANDLER__H
#include <message_.h>
// virtual base class for handlers
class handler_ {
public:
        handler_(){}
        virtual ~handler_(){}
        virtual const int handle(message_* m){return 0;}
};
#endif
//--------------------------------------//
// sender_.h
#ifndef SENDER__H
#define SENDER__H
#include <handler_.h>
#include <message_.h>
// virtual base class for sender types
class sender_ {
public: 
        sender_(){}
        virtual ~sender_(){}
        virtual int  send(message_* m) const { return 0;}
        virtual bool register_handler(handler_* h) { return false;}
        virtual bool unregister_handler(handler_* h) { return false;}   
};
#endif
//--------------------------------------//
// handler.h
ifndef HANDLER_H
#define HANDLER_H
#include <handler_.h>
#pragma interface
/**
    base class for msg handling. It is a Command,
    that execute the handle(message_* m) function.
    In the derived classes, you have to cast
    the message to the appropriate type, using the cast()
    f., that returns a null pointer if the cast fails. 
    Remember to check for that in handling!
*/
template <typename MSG>
class handler : public virtual handler_ {
public:
        handler();
 
    // function to override for msg handling
    virtual int handle(message_* m) const;
    /*
       function to override to change cast routine,
       (you don't usually need it, but...)
    */
    virtual MSG * cast(message_ * m) const;
        virtual ~handler();
};
#endif
//--------------------------------------//
// handler.hxx
#pragma implementation "handler.h"
#include <sender.h>
template <typename MSG>
handler<MSG>::handler()  : handler_(){}
// default handling does nothing
template <typename MSG>
int handler<MSG>::handle(message_* m) const{
    return -1;
}
// retrieves the correct pointer to a type
template <typename MSG>
MSG * handler<MSG>::cast(message_ * m) const{
    try{
        MSG * msg = dynamic_cast<MSG *>(m);
        if (!msg);
            //warning
        return msg;
    } catch (...){
        // warning
        return 0;
    }
}
// unregister the handler to avoid SIGSEV
template <typename MSG>
handler<MSG>::~handler(){
    sender<MSG>* s = &(sender<MSG>::get_instance());
    s->unregister_handler(this);
}
//--------------------------------------//
// message.h
#ifndef MESSAGE_H
#define MESSAGE_H
#include <message_.h>
/**
  template class for the msg basic types,
  you should derive another class for each type of msg,
  otherwise you can only have one sender for a
  message<std::string>, whilst you may need more
  e.g.:
  ErrorMessage : MESSAGE(std::string);
  WarningMessage : MESSAGE(std::string);
  ErrorHandler : HANDLER(ErrorMessage);
  WarningHandler : HANDLER(WarningMessage);
  the handlers for errors could stop the program,
  whilst the warnings handlers only give warning. By 
  using two classes, each will receive only appropriate msgs,
  otherwise handlers will intercept any string message raised!
*/
template <typename DATA>
class message : public virtual message_  {
    /*
       d stores msg data, you could obviously add
       more data in derived classes, but it's 
       simpler store them in a struct, and pass it
       as template argument.
       More useful are data manipulation member functions.
    */
    DATA d;
    // not impl.
    message(const message<DATA>&);
    void operator =(const message<DATA>&);
public:
    message(const DATA& data) : message_(), d(data){}
    virtual ~message(){}
    virtual const DATA& Data() const {return d;}
};
#endif
//--------------------------------------//
// sender.h
// TODO: add a mutex to the container operation for thread safety
#ifndef SENDER_H
#define SENDER_H
#include <set>
#include <sender_.h>
#include <handler.h>
/*
    sender uses the Observer and singleton patterns, and acts 
    as a router for msg it receives. Handlers of the messages
    have to register to the correct sender, otherwise they 
    will receive msgs of wrong type.
    See main.cpp for details.
*/
template <typename MSG>
class sender : public virtual sender_{
public:
    // container and iterator types declaration
    typedef std::set< const handler< MSG >* >  Container;
    typedef Container::iterator Iterator;
private:
    // set of registered handlers
    Container handlers;
    // private construction guarantees one singleton instance
    sender<MSG>() : sender_(){}
    // not impl.
    sender<MSG>(const sender<MSG>&);
    void operator =(const sender<MSG>&);
public:
    virtual ~sender(){}
    // adds a handler to the listeners set
    virtual bool register_handler(const handler<MSG>* m){
        if (handlers.find(m)!=handlers.end() ){
            return false;
        }
        handlers.insert(m);
        return true;
    }
    // removes a handler from the listeners set
    virtual bool unregister_handler(const handler<MSG>* m){
        if (handlers.find(m)!=handlers.end()) {
            handlers.erase(m);
            return true;
        } else {
            return false;
        }
    }
    /*
      dispatch the msg to all registered listeners,
      acts like the notify() f. of the observer
    */
    virtual int send(message_* m) {
        Iterator it = handlers.begin();
        for (; it!=handlers.end(); ++it){
            // execute the appropriate handling f.
            (*it)->handle(m);
        }
        return 0;
    }
    // the only way to get a reference to the singleton
    static sender<MSG>& get_instance(){
        static sender<MSG> s;
        return s;
    }
};
#endif // !defined SENDER_H
//--------------------------------------//
// messagecenter.h
#include <message.h>
#include <handler.hxx>
// some macros to simplify code writing
// used in msg class declaration
#define MESSAGE(TYPE) public message<TYPE>
// used in msg class constructor implementation
#define MSG(TYPE, INST) message<TYPE>(INST)
// used in handler class declaration
#define HANDLER(TYPE) public handler<TYPE>
// short to get singleton instances
#define SENDER(TYPE) sender<TYPE>::get_instance()
//--------------------------------------//
// main.cpp
#include <string>
#include <iostream>
#include <cmath>
#include <messagecenter.h>
// how to declare a message type
class stringmessage : MESSAGE(std::string) {
public:
    stringmessage(const std::string& s) : MSG(std::string, s) {}
};
// another message
class intmessage : MESSAGE(int) {
public:
    intmessage(const int& i) : MSG(int, i) {}
};
// how to declare a handler
class string_handler : HANDLER(stringmessage) {
public:
    int handle(message_ *m) const{
        stringmessage* msg = cast(m);
        // IMPORTANT: this check avoids SIGSEV
        if (!msg){
            std::cout << "string_handler  " << std::hex << this << " : " 
<< m;
            std::cout << " is not a valid stringmessage" << std::endl;
            return -1;
        }
        std::cout << msg->Data() << std::endl;
        return 0;
    }
};
// another handler
class string_inverter : HANDLER(stringmessage){
public:
    int handle(message_ *m) const{
        stringmessage* msg = cast(m);
 
        if (!msg){
            std::cout << "string_inverter " << std::hex << this << " : " 
<< m;
            std::cout << " is not a valid stringmessage" << std::endl;
            return -1;
        }
        const std::string& src = msg->Data();
        std::string::const_reverse_iterator it = src.rbegin();
        std::string res("");
        while(it != src.rend()){
            res += *it;
            ++it;
        }
        std::cout << res << std::endl;
        return 0;
    }
};
// yet another handler
class primefinder : HANDLER(intmessage){
public:
    int handle(message_ *m) const{
        intmessage* msg = cast(m); 
        if (!msg){
            std::cout << "primefinder     " << std::hex << this << " : " 
<< m;
            std::cout << " is not a valid intmessage" << std::endl;
            return -1;
        }
        int i = msg->Data();
        if (i <= 0){
            std::cout << "Error: " << std::dec << i << " is not a 
positive number" << std::endl;
            return 0;
        }
        if ((i % 2) == 0){
            std::cout << std::dec << i << " is not prime: is pair" << 
std::endl;
            return 0;
        }
        int root = (int)sqrt((double)i);
        for(int step= 3; step <= root; step +=2){
            if((i % step) == 0){
                std::cout << std::dec << i << " is not prime: " << step 
<< " is a factor of " << i << std::endl;
                return 0;
            }
        }
        std::cout << std::dec << i << " is prime." << std::endl;
        return 0;
    }
};
int main(int argc, char *argv[]){
  // first, we create some msgs
  message_ * smsg  = new stringmessage(std::string("Hello, World! 
desrever eb lliw siht") );
  message_ * imsg1 = new intmessage((int)-2);
  message_ * imsg2 = new intmessage((int)4);
  message_ * imsg3 = new intmessage((int)77);
  message_ * imsg4 = new intmessage((int)67);
  //and some handlers
  handler<stringmessage> * h1 = new string_handler();
  handler<stringmessage> * h2 = new string_inverter();
  handler<intmessage>    * h3 = new primefinder();
  // we get the sender instances for our messages
  sender<stringmessage>  * s1 = &(SENDER(stringmessage));
  sender<intmessage>     * s2 = &(SENDER(intmessage));
  // and we register the handlers
  s1->register_handler(h2);
  s1->register_handler(h1);
  s2->register_handler(h3);
  /* 
     finally, we can send the msgs!
     lets see what happens... these lines should give 3 errors
     NOTE: if you have a masochistic attitude, try to remove
           the checks in the handling functions...
  */
  s1->send(imsg1);
  s2->send(smsg);
  // lets do thing correctly
  s1->send(smsg);
  s2->send(imsg1);
  s2->send(imsg2);
  s2->send(imsg3);
  s2->send(imsg4);
  /*
     ...cleanup...
    (it is strongly suggested to use autopointer
     or automatic deletion in real programs...)
  */
  delete h1;
  delete h2;
  delete h3;
  delete smsg;
  delete imsg1;
  delete imsg2;
  delete imsg3;
  delete imsg4;
 
  return 0;
}

 mi sarei potuto fare un'idea più precisa.
Se vuoi un abbozzo di codice, te lo scrivo al volo.
Eccolo qui. I nomi delle classi e dei metodi dovrebbero essere
sufficientemente esplicativi. Non ricalca completamente il pattern
Observer nella sua forma canonica, ma l'idea di base è la stessa.
  // classi basi astratte
  class event_handler;
  class event_dispatcher
  { public:
    virtual void register_handler( event_handler* ) = 0;
    virtual void notify_event( int ) = 0;
    virtual ~event_dispatcher() { }
  };
  class event_handler
  { public:
    virtual void handle_event( int ) = 0;
    virtual ~event_handler() { }
  };
  // classi concrete
  class My_event_dispatcher: public event_dispatcher
  {
    typedef std::vector< event_handler* > container_t;
    container_t     handlers_;
    public:
    virtual void register_handler( event_handler* hndl )
    {   handlers_.push_back( hndl );
    }
    virtual void notify_event( int value )
    {   container_t::const_iterator i = handlers_.begin();
        container_t::const_iterator end = handlers_.end();
        for( ; i != end; ++i )
        {       (*i)->handle_event( value );
        }
    }
  };
  class My_event_handler : public event_handler
  {
    public:
    virtual void  handle_event( int value )
    {   cerr << this << ": handle_event " << value << endl;
    }
  };
  int main()
  {
          My_event_handler     handler1;
          My_event_handler     handler2;
          My_event_dispatcher   dispatcher;
          dispatcher.register_handler( &handler1 );
          dispatcher.register_handler( &handler2 );
          dispatcher.notify_event( 10 );
  }
Max

 Max
Diego.

 Observer nella sua forma canonica, ma l'idea di base è la stessa.
tanto per amor di discussione: recentemente, alcuni grossi nomi, tipo 
Coplien, Eckel, hanno ribadito fortemente che quel ci si aspetta da un 
pattern e' la soluzione di un dato problema, in un dato contesto, nel 
modo migliore e piu' efficiente. Per cui non esistono "forme canoniche" 
dei pattern, anzi, essi mutano continuamente a seconda del linguaggio, 
del contesto, delle necessita'
Un esempio e' la classe Borg di Alex Martelli, citata da eckel in 
Thinking in Python, che implementa il pattern singleton avendo piu' 
istanze, tutte con lo stesso stato interno, e cambiandolo ad una, lo
cambiano tutte

 cambiano tutte
Infatti Borg (o Stateless Proxy) non implementa il pattern Singleton, ma 
e' un altro pattern che ha alcune caratteristiche in comune con esso.
Ciao,
Nicola

 esso.
appunto, ma se a me serve un sinlgeton, e' l'"effetto" che ha che mi 
serve, almeno per come la vede Eckel
cito:
   "(...)What we really want with a Singleton is to have a single set of 
    state data for all objects. That is, you could create as many 
    objects as you want and as long as they all refer to the same state 
    information then you achieve the effect of Singleton"
personalmente, credo che se in un linguaggio e' piu' 
semplice/efficiente/quelchevuoi usare uno stateless proxy piuttosto che 
un singleton per mutuarne gli effetti, ben venga.

 un singleton per mutuarne gli effetti, ben venga.
Singleton in realta' e' un pattern particolarmente sfortunato, perche' 
nelle poche righe della sua implementazione in C++ fa molte piu' cose 
del dovuto e solleva tutta una serie di problemi specifici. Forse la GoF 
avrebbe dovuto semplicemente scrivere:
...
private:
   static SingletonType singleton;
...
Ma qui si aprirebbe tutto un luuuuuungo discorso.
Ciao,
Nicola