Newsgroup: it.comp.lang.c++
Titolo: Funzioni callback

Ciao a tutti,
in un mio programma voglio creare un timer che ad ogni intervallo 
richiami un metodo di una mia classe. Inutile dirvi che non ci sono 
riuscito. Riesco solo se faccio questo metodo static ma se lo faccio 
static devo creare static l'intera classe... cosa che non posso fare.
Che stratagemmi devo usare? Mi sono scervellato per un po' ma non mi č 
venuto in mente niente... ma non sono un granchč per ora col C++ quindi 
chiedo ai GURU della rete! :-)
Saluti,
   Mirko.


Tieni conto che per chiamare un metodo non static di una classe hai
bisogno di un oggetto istanziato di quella classe.
class miaclasse { // ... };
miaclasse miaistanza;
// ...
miaistanza.miometodo();
Quindi una soluzione semplice è creare un oggetto globale della tua
classe, a cui può accedere il tuo timer.
Comunque non sono esperto di funzioni callback, probabilmente c'è
qualche altro metodo migliore.
Ecco qualcosa di più elaborato che mi è venuto in mente:
#include <map>
#include <cassert>
#include <iostream>
class callback;   // forfard declaration
namespace impl
{
   typedef std::map<int, callback*> call_map;
   call_map to_call;   // variabile globale
}
class callback
{
   private:
      static int count;
      int id_;
   public:
      callback()
      : id_(count)
      {
         // aggiunge questa istanza a to_call
         impl::to_call[id_] = this;
         ++count;
      }
      ~callback()
      {
         using impl::to_call;
         assert( to_call.find(id_) != to_call.end() );
         // elimina questa istanza da to_call
         to_call.erase(id_);
      }
      // funzione callback
      // nota: non è una funzione virtuale pura, ma ha un
      // comportamento di default
      virtual void callable(int) {  };
      // identificatore univoco di una particolare istanza
      // di questa classe
      int id() const { return id_; }
};
int callback::count = 0;
class test: public callback
{
   private:
      int i;
   public:
      test(int ii)
      : i(ii) {  }
      // funzione callback
      void callable(int k)
      {
         std::cout << "test::callable(" << k << ")\t";
         std::cout << "i = " << i << '\n';
      }
};
int main()
{
   test t(999);
   std::cout << "
t.id
 = " << 
t.id
() << '\n';
   impl::to_call[0]->callable(8);
   std::cout << "\n\n";
   test t2(777);
   std::cout << "
t2.id
 = " << 
t2.id
() << '\n';
   impl::to_call[1]->callable(5);
   // chiama callable per tutti gli oggetti istanziati
   std::cout << "\n\n";
   typedef impl::call_map::const_iterator CI;
   for(CI p = impl::to_call.begin(); p != impl::to_call.end(); ++p)
   {
      std::cout << "id = " << p->first << '\n';
      p->second->callable(0);
   }
}
Tieni presente che è solo una idea.
Una cosa facile da aggiungere è il 'permesso' di
chiamare la funzione callback per una certa istanza.
Inoltre la variabile globale to_call andrebbe 'trattata'
meglio, specialmente se vi si deve accedere da diverse
translation unit.
Se qualcosa non ti è chiaro, fammelo sapere.
Manlio Perillo

 static devo creare static l'intera classe... cosa che non posso fare.
Che significa "creare static l'intera classe"? Non ha senso. Puoi
tranquillamente dichiarare static una sua funzione membro e poi
chiamarla attraverso il nome della classe stessa (es.:
MyClass::MyFunction()).
EMG

static devo creare static l'intera classe... cosa che non posso fare.
Non ho compreso l'ultima tua frase, ma mi sto interessando allo stesso tipo
di problema (callback). Se mi contatti in privato credo di poterti essere
utile.
Ciao
Domenico Barra

 EMG
Hai ragione non è questa la situazione, è solo come dice Manlio nel 
messaggio successivo che devo creare un'istanza della mia classe 
altrimenti non ha senso avere un metodo static.
Quindi mi era venuto in mente che forse potrei avere una classe globale 
che gestisce i timers a cui magari è possibile passare l'indirizzo di un 
membro della propria classe da richiamare... è un'idea che mi è venuta 
ora... non so quanto sia corretta.
Saluti,
   Mirko.

 [...]
Ok molto interessante, me lo studio per bene e poi ti faccio sapere 
eventuali problemi! 
Grazie,
   Mirko

 altrimenti non ha senso avere un metodo static.
Invece il metodo static può essere chiamato anche in assenza di
qualsiasi istanza. Serve proprio a quello. O forse non ho capito io il
problema...
EMG

 static devo creare static l'intera classe... cosa che non posso fare.
Un approccio usato da alcuni framework (come quello che cito in fondo)
potrebbe essere approssimativamente riconducibile a queste due interfacce:
 class timeout_handler
 {
  public:
  virtual int handle_timeout() = 0;
 };
 class timer
 {
    public:
    virtual int register( timeout_handler* ptr) = 0;
    virtual int unregister( timeout_handler* ptr) = 0;
 };
L'idea è che un timeout_handler viene registrato o deregistrato in un
timer, il quale si occupa di invocare handle_timeout() al momento
opportuno.
Ad es:
 class standard_timer: public timer
 {
     public:
    // interfaccia timer
    int register( timeout_handler* ptr) 
    {
         /* inseriamo  ptr in un qualche tipo di tabella */
    }
     int unregister( timeout_handler* ptr) 
    {
       /* togliamo ptr ... */
    }
    void  run() 
    {
       // qui potrremmo semplicemente attendere che scada il timeout e
       // invocare  gli handle_timeout degli oggetti registrati,
       //  oppure usare un nostro metodo statico e un riferimento a this
       //  per installarci come handler di SIGALRM o equivalente... 
     }
 };
Quindi...
 class Pippo: public timeout_handler
 {
    // ci mettiamo quello che vogliamo, oltre chiaramente a
    // un'implementazione di handle_timeout()
    // volendo, potremmo fare anche un ctor e un dtor così:
    public:
    Pippo( timer* tp ) : m_timer(tp)
    {
         m_timer->register(this);
    }
    ~Pippo()
    {
         m_timer->unregister(this);
    }
    private:
    timer*     m_timer;    
 };
 int main()
 {
  standard_timer	aTimer;
  Pippo                 p( &aTimer );
  aTimer.run();
 }
Ovviamente, visto quello che fanno il ctor e il dtor di Pippo, è cruciale
che il lifetime dello standard_timer copra quello dell'oggetto Pippo.
Naturalmente nella realtà le cose sono più complicate di così. E' ovvio ad
esempio che si deve poter assegnare ad ogni oggetto registrato un timeout
differente. E' ragionevole inoltre che un tale meccanismo venga
incorporato in un sistema più generale per il demultiplexing degli eventi
di vario tipo.
A questo proposito, se vuoi usare (o studiare) pattern e implementazioni
efficaci e collaudate ti suggerisco di dare un'occhiata a ACE
(
http://www.cs.wustl.edu/~schmidt/ACE.html
).
Max

 [...]
Manlio Perillo

 richiami un metodo di una mia classe.
Ho trovato quest'esempio di callback:
/* Code for exercise 14.10.
 |
 | "C++ Solutions--Companion to The C++ Programming Language,
 | Third Edition" by David Vandevoorde; Addison-Wesley 1998;
 | ISBN 0-201-30965-3.
 |
 | Permission to use, copy, modify, distribute and sell this
 | software and its documentation for any purpose is hereby
 | granted without fee.
 | The author makes no representations about the suitability
 | of this software for any purpose.  It is provided "as is"
 | without express or implied warranty.
 `----------------------------------------------------------------*/
#include <stdio.h>
struct Callback {
   virtual void operator()() = 0;
   virtual void operator()() const = 0;
};
template<void (*PF)()>
struct CallbackFunction: Callback {
   virtual void operator()() { PF(); }
   virtual void operator()() const { PF(); }
};
template<typename T, void (T::*MF)()>
struct ObjectCallback: Callback {
   ObjectCallback(T &object): object_(object) {}
   virtual void operator()() { (object_.*MF)(); }
   virtual void operator()() const { (object_.*MF)(); }
private:
   T &object_;
};
void a() { printf("Hello\n"); }
void b() { printf("World!\n"); }
struct X {
   X(char const *p, char const *q): p_(p), q_(q) {}
   void f() { printf("%s\n", p_); }
   void g() { printf("%s\n", q_); }
private:
   char const *p_, *q_;
};
void greet(Callback const &first, Callback const &second) {
   first();
   second();
}
int main() {
   greet(CallbackFunction<&a>(), CallbackFunction<&b>());
   X x("Hello", "Class!");
   greet(ObjectCallback<X, &X::f>(x), ObjectCallback<X, &X::g>(x));
}
Per il tuo caso, è possibile una cosa del genere:
class tua_classe
{
    // ...
    public:
        void to_be_called() { // ... }
    // ...
};
class Timer
{
    private:
        Callback* to_call;
    public:
        Timer(Callback& c)
        : to_call(&c) {  }
        void run()
        {
            // ...
            (*to_call)();
        }
};
int main()
{
    tua_classe x;
    Timer t(ObjectCallback<tua_classe, &tua_classe::to_be_called>(x);
}
Nota:
Callback usa un void come return value, ma è facile fare in modo che
ritorni un tipo qualsiasi (ammesso che serva)
Manlio Perillo

 Forse la tabella deve essere static?
Al contrario, l'idea era proprio evitare questo, affinchč si potesse,
volendo, avere piů timer indipendenti, attivabili e disattivabili a
piacere, ognuno con una sua tabella separata.
Max


[...]
Ah, ora mi č chiaro.
Quindi si possono avere piů timer indipendenti e scegliere
quale istallare.
E' una tecnica molto interessante
(non č mica coperta da copyright? :-).
Manlio Perillo

Ciao,
so di essere OT per cui brevemente: vi ringrazio tutti per l'aiuto 
appena ho messo gių qualcosa di buono ve lo posto!
Saluti,
   Mirko.

    Timer t(ObjectCallback<tua_classe, &tua_classe::to_be_called>(x);
In questo caso viene passato un oggetto temporaneo di tipo
ObjectCallback al costruttore del timer. Questo oggetto non
rischia di durare molto meno del timer ? ... se questo succede
sono grane visto che il timer si trova un puntatore che
e' stato invalidato.
Inoltre il compilatore dovrebbe segnalare che viene usato un
temporaneo per inizializzare un non-const reference (gcc lo fa).
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff


Il DJGPP non mi da nessun warning (nemmeno con -Wall)...
Mentre il BCC 5.5 non compila nemmeno l'esempio originale
(Error 22: Only member functions may be 'const' or 'volatile')
Nota: la riga 22 è:
template<void (*PF)()>
....
Fermo restando il fatto che la tua osservazione è giusta,
vorrei capire ora perchè il DJGPP non mi dà nessun warning.
Manlio Perillo