Newsgroup: it.comp.lang.c++
Titolo: Passare l'indirizzo di un metodo di una classe

Salve,
 ho la necessita' di passare l'indirizzo di un metodo di una classe ad un metodo di una altro,
con un operazione del genere:
***Metodo che riceve l'indirizzo e' definito cosi':
classe Esempio{
...
void Metodo(bool (*lp)() );
...
};
***Metodo da cui prendo l'indirizzo e' cosi':
class Sorgente{
...
bool indirizzo();
...
};
***Istruzione incriminata:
...
Esempio->metodo(indirizzo); // oppure Esempio->metodo(Sorgente::indirizzo);
Qui' ho errore :(
Se invece indirizzo lo definisco come "static", l'istruzione va': come mai?
Dato che la definizione come static mi e' "scomoda", che altro posso fare?
Uso Visual C++ 6
Grazie
Killer


wrote:


 Dato che la definizione come static mi e' "scomoda", che altro posso fare?
Giusto oggi è stato postato qualcosa riguardo ai puntatori a funzione
membro, cerca il thread "Errore sui puntatori a funzione".
Massimo


Nel tuo esempio dovresti dichiarare l´argomento di metodo non come un
semplice puntatore a funzione ma come puntatore a funzione membro della
classe Sorgente.
Controlla se va cosi´, non l´ho compilato:
// "predichiarazione"
#include <iostream.h>
class Sorgente;
class Esempio{
//...
public:
   void metodo(bool (Sorgente::*lp)(void) );
//...
};
bool indirizzo(){cout << "Salve sono indirizzo()" << endl; return 0;};
//***Metodo da cui prendo l'indirizzo e' cosi':
class Sorgente{
//...
public:
   bool indirizzo(){cout << "Salve sono indirizzo()" << endl; return 0;};
   int pippo;
//...
};
void Esempio::metodo(bool (Sorgente::*lp)(void) )
{
   cout << "Salve sono metodo()" << endl;
   Sorgente s;
   Sorgente *p = &s;
   (p->*lp)();
}
int main()
{
//***Istruzione incriminata:
//...
   Esempio esempio;
   Sorgente sorgente;
   sorgente.pippo = 3;
   esempio.metodo(sorgente.indirizzo);
   return 0;
}

 membro, cerca il thread "Errore sui puntatori a funzione".
Cercato e letto, come anche letto quello precedente " [help] si può passare
l'indirizzo di un membro?", ma cmq non ho trovato una risposta esauriente,
e ti spiego perche':
1- posso definire il metodo da passare "static", ma ogni volta che uso qualche
variabile/metodo mi dice "deve essere static" (+ o -) e la cosa quindi si fa' piu'
contorta;
2- posso passare l'indirizzo come "&Sorgente::indirizzo", ma questo prevede che
dall'altra parte lo intercetti con qualcosa del tipo &Sorgente::qualchecosa.
Il problema e' che il ricevente NON sa' a priori da dove venga quell'indirizzo,
percio' una cosa del genere mi fa' perdere il carattere di "generalita' " dei puntatori
a funzione...
Bho! Al momento non so' come fare... Forse quando imparero' meglio il C++ trovero'
una risposta.
Cmq grazie dell'aiuto
Killer

 avrete tutte le risposte che cercate ............. .
SCUSA SCUSA SCUSA!!!! (dico sul serio)
Stavo per andare all'universita' e sono stato impulsivo...
Hai ragione, dovevo leggere i messaggi precedenti!
Come aggravante, il messaggio era anche recente :/
La prossima volta saro' piu' diligente :)
P.S.:cmq non ho trovato ancora una risposta, sara' che sono
un po' "lento" a capire e/o che non ho ancora molta dimistichezza col C++..
Killer

 classe Sorgente.
Sai qual'e' l'inghippo? E' che io voglio usare i puntatori proprio perche' non
so' a priori da dove viene la funzione che voglio passare a metodo :/
Puo' essere un metodo di Sorgente, come un metodo ad un'altra classe o
una normale funzione (per lasciare piena liberta' a chi poi utilizzera la classe
(che poi sarei sempre io :) ) ).
Quindi anche se facessi come dici tu, alla fin fine non sarei soddisfatto :)


 Quindi anche se facessi come dici tu, alla fin fine non sarei soddisfatto
:)
In C++, non puoi usare in modo direttamente intercambiabile
un puntatore "a normale funzione" ed un puntatore a metodo
(non statico: i metodi statici di una classe _sono_ normali
funzioni, eccetto al massimo per questioni di nomi e di
accessibilita`).  Se vuoi garantire la possibilita` di un
uso polimorfico fra l'una e l'altra cosa, devi incapsularle
entrambe opportunamente.
Ad esempio, supponendo non interessi passare argomenti o
ritornare risultati dall'"oggetto-chiamabile" in gioco
(attenzione, codice non testato, possibili errorucci):
// l'interfaccia astratta che riduce a fattor comune
// "oggetti chiamabili" perfettamente generali (a parte
// il dettaglio degli argomenti e valori di ritorno:-):
class Callable {
public:
    virtual void operator()(void) const = 0;
};
// concretizzazione di Callable per una funzione
class FunctionCallable: public Callable {
    typedef void (*func_t)(void);
    func_t f;
public:
    virtual void operator()(void) const { f(); }
    virtual FunctionCallable(func_t f): f(f) {}
};
// concretizzazione di Callable per un metodo
// di una qualsiasi classe (templatizzata)
template <class T>
class MethodCallable: public Callable {
    typedef void(T::*method_t)(void);
    method_t m;
    mutable T& t;
public:
    virtual void operator()(void) const { (t.*m)(); }
    virtual MethodCallable(T& t, method_t m): t(t), m(m) {}
};
// comoda funzione per la deduzione di tipi
template <class T>
MethodCallable makeCallable(T& t, void(T::*m)(void)) {
    return MethodCallable<T>(t,m);
}
Le tue funzioni che vogliono potere indifferentemente
usare funzioni o metodi-piu`-istanze dovranno dunque
ricevere l'oggetto chiamabile come "const Callable&"
(e, se occorre mantenerlo come parte di un qualche
stato, esso dovra` pur sempre essere tenuto per reference
o puntatore, per garantire il comportamento polimorfico;
d'altronde, non si puo` sbagliare, in quanto naturalmente
abbiamo accuratamente resa non-instanziabile la classe
astratta 'Callable':-).
I chiamanti potranno passare direttamente un puntatore
a funzione (che non accetti argomenti e non torni nulla,
nel nostro esempio, ma spero sia ovvio come si possa
generalizzare questo a UN qualsiasi set fisso di tipi
argomento e tipo ritorno), che verra` convertito in
(const reference a) istanza di FunctionCallable dal
costruttore (apposta e` monoargomento non explicit)
di questa classe -- questo non e` bellissimo, e si
potrebbe aggiungere un overload di makeCallable e fare
explicit quel costruttore (ma gli overload di template
con non-template non sono privi di problemi).  Oppure,
il chiamante potra` usare la makeCallable per passare
(un reference a) una istanza di una qualche classe e un
puntatore a metodo relativo a quella stessa classe.
(Si possono aggiungere anche versioni in grado di
chiamare [solo metodi const] su reference const a
istanze).
Tutto questo va secondo me considerato con notevole
spirito critico.  Si tratta di infrastruttura piuttosto
complessa, che si aggeggia insieme allo scopo, piu` o
meno, di offrire "zucchero sintattico" a ipotetici utenti
del nostro sottosistema.  L'infrastruttura usa poi degli
aspetti del C++ Standard che possono piu` facilmente
solleticare bug del compilatore, proprio perche` non
sono gli aspetti piu` pane-e-burro usati ogni giorno
(questo si applica, a mio vedere, a *OGNI* uso di
puntatori-a-metodo: quel che succede in presenza, ad
esempio, di overload fra metodi, puo` essere tutt'altro
che chiaro al futuro manutentore del codice E magari
anche all'autore del compilatore, che facilmente puo`
averci lasciato qualche bug...).
Il C++ ti permette di fare "di tutto di piu`", ma si
paga un prezzo enorme in termini di complicazione.  Vale
la pena?  9 volte su 10, no; si possono ottenere enormi
semplificazioni (e quindi codice MOLTO piu` solido, anche
considerando possibili bug dei compilatori) a prezzo
molto modesto, ad esempio semplicemente richiedendo al
codice-cliente di essere piu` esplicito relativamente a
cosa, esattamente, stia richiedendo.
Queste complicazioni *bisogna* conoscerle, per essere
un 'vero' programmatore C++ a tutti gli effetti: ma,
piu` spesso che no, si applica poi l'ottima regola "se
le conosci, le eviti ... se le conosci, non ti
uccidono"!-).  [Naturalmente in practica uno poi non
manca di cadere ripetutamente nella tentazione di
"tirare palle curve" e farsi bello con la propria
padronanza di tutte queste complessita` -- ma dopo
qualche anno di zuccate contro i muri, e sufficienti
bozzi e cicatrici per non scordarsele, si finisce per
andarci MOLTO, ma MOLTO piu` cauti ad usarle davvero
in codice di produzione...!-)].
Alex -- MVP C++ di Brainbench

Killer
Ok Killer,
magari possiamo risolvere facilmente con un overload il problema di ricevere
il puntatore da una normale funzione, ma non saprei come risolverlo per il
caso in cui tu voglia passare il puntatore di una funzione membro di
un´altra classe.
Magari si potrebbe dichiarare il parametro come puntatore ad una funzione
virtuale membro di una classe base, dalla quale derivare altre classi.
Per poi passare alla chiamata, il puntatore alla funzione membro di una
delle classi derivate.
Per ora ti do´ il codice che risolve parzialmente il problema , anche per
funzioni generiche non membro mediante un semplice overload:
#include <iostream.h>
class Sorgente;
class Esempio{
//...
public:
   void metodo(bool (Sorgente::*lp)(void) );
   void metodo(bool (*lp)(void) );
//...
};
bool indirizzo(){cout << "Salve sono indirizzo() globale" << endl; return
0;};
//***Metodo da cui prendo l'indirizzo e' cosi':
class Sorgente{
//...
public:
   bool indirizzo(){cout << "Salve sono indirizzo() membro" << endl; return
0;};
   int pippo;
//...
};
void Esempio::metodo(bool (Sorgente::*lp)(void) )
{
   cout << "Salve sono metodo() con parametro bool (Sorgente::*lp)()" <<
endl;
   Sorgente s;
   Sorgente *p = &s;
   (p->*lp)();
}
void Esempio::metodo(bool (*lp)(void) )
{
   cout << "Salve sono metodo() con parametro bool(*lp) ()" << endl;
   lp();
}
int main()
{
//***Istruzione incriminata:
//...
   Esempio esempio;
   Sorgente sorgente;
   sorgente.pippo = 3;
   esempio.metodo(sorgente.indirizzo);
   esempio.metodo(indirizzo);
   return 0;
}

Ciao Alex,
ho voluto provare il tuo suggerimento ma ho un problema.
Quando devo chiamare la funzione che mi accetta come parametro il tuo
const Callable &
dovro´ passargli come argomento un puntatore a funzione generica o un
puntatore a funzione membro cosi´ come da ipotesi.
Ma ovviamente il compilatore si lamenta perche´ ho definito la funzione in
modo che accetti un const Callable & appunto.
Allora penso di non aver capito come applicare il tuo suggerimento e ti
invio il codice con i 2 errori in compilazione:
#include <iostream.h>
// l'interfaccia astratta che riduce a fattor comune
// "oggetti chiamabili" perfettamente generali (a parte
// il dettaglio degli argomenti e valori di ritorno:-):
class Callable {
public:
    virtual void operator()(void) const = 0;
};
// concretizzazione di Callable per una funzione
class FunctionCallable: public Callable {
    typedef void (*func_t)(void);
    func_t f;
public:
    virtual void operator()(void) const { f(); }
    FunctionCallable(func_t ft): f(ft) {}
};
// concretizzazione di Callable per un metodo
// di una qualsiasi classe (templatizzata)
template <class T>
class MethodCallable: public Callable {
    typedef void(T::*method_t)(void);
    method_t m;
    mutable T& t;
public:
    virtual void operator()(void) const { (t.*m)(); }
    MethodCallable(T& tt, method_t mm): t(tt), m(mm) {}
};
// comoda funzione per la deduzione di tipi
template <class T>
MethodCallable<T> makeCallable(T& t, void(T::*m)(void)) {
    return MethodCallable<T>(t,m);
}
class Sorgente;
class Esempio{
//...
public:
   void metodo(void (Sorgente::*lp)(void) );
   void metodo(void (*lp)(void) );
   void alexmetodo(const Callable &c) {c();};
//...
};
void indirizzo(){cout << "Salve sono indirizzo() globale" << endl;};
//***Metodo da cui prendo l'indirizzo e' cosi':
class Sorgente{
//...
public:
   void indirizzo(){cout << "Salve sono indirizzo() membro" << endl;};
   int pippo;
//...
};
void Esempio::metodo(void (Sorgente::*lp)(void) )
{
   cout << "Salve sono metodo() con parametro bool (Sorgente::*lp)()" <<
endl;
   Sorgente s;
   Sorgente *p = &s;
   (p->*lp)();
}
void Esempio::metodo(void (*lp)(void) )
{
   cout << "Salve sono metodo() con parametro bool(*lp) ()" << endl;
   lp();
}
int main()
{
//***Istruzione incriminata:
//...
   Esempio esempio;
   Sorgente sorgente;
   sorgente.pippo = 3;
   esempio.alexmetodo(sorgente.indirizzo);
   esempio.alexmetodo(indirizzo);
   return 0;
}
C:\mauro\source\faq\line.cpp(89) : error C2664: 'alexmetodo' : cannot
convert parameter 1 from 'void (void)' to 'const class Callable &'
C:\mauro\source\faq\line.cpp(90) : error C2664: 'alexmetodo' : cannot
convert parameter 1 from 'void (void)' to 'const class Callable &'
In che modo dovrei modificare la chiamata o la funzione alexmetodo() ?
Grazie per l´attenzione.
Mauro

 funzioni generiche non membro mediante un semplice overload:
Grazie dell'aiuto, ma penso di seguire il consiglio di Alex Martelli
(cioe' non fare tutto quello che mi ha scritto, che mi studiero' con calma
perche' con il mio stato di conoscenza attuale non ci ho capito un gran che :) )
di evitare complicazioni e realizzare un codice + semplice e facilmente
manutenibile :)
Cmq grazie a tutti, e come dice il buon vecchio Kintaro
"Ho imparato qualcosa" :)
Killer

 in codice di produzione...!-)].
Grazie mille per il codice che mi hai mandato: benche' al momento
mi sia quasi completamente indecifrabile, sara' sicuramente
importante per capire meglio il C++, linguaggio che mi sta'
appassionando molto :)
Credo che seguiro' il consiglio di evitare, quando possibile, di
perdermi nel complicare cio' che scrivo (anche se adesso non e'
che programmi niente di complicato :) ) e, naturalmente, di studiarmi
meglio il C++ :)
Grazie ancora
--
Killer
"Ho imparato qualcosa" :)

in codice di produzione...!-)].
Condivido... Ho provato a scrivere codice serio che usasse
qualcosa di questi angoli bui e trovare una versione che
andasse bene a tre compilatori fra i piu' diffusi (ho
detto 3, non 30) e' stata un'impresa.
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

   void call() { p->call(); }
In effetti mi pare scorretto: il parametro di un template o e' un tipo o
e' un non tipo. I due non sono direttamente combinabili. Non so nemmeno
se una sintassi del tipo:
template <typename T, T * p> void call() { p->call(); }
sia valida. Puoi provare a chiedere ai maghi dei template su c.l.c++.m
Ciao,
Nicola

 sia valida. Puoi provare a chiedere ai maghi dei template su c.l.c++.m
Per la cronaca:
MSVC6 e gcc compilano senza problemi questo:
 template<class T, T* p > void print() { cerr << *p; }
 std::string s = "ciao";
 int
 main()
 {
  print<std::string,&s>();
 }
MSVC6 compila anche questo:
 template<class * p >  void print() { cerr << *p; }
 std::string s = "ciao";
 int
 main()
 {
	print<&s>();
 }
 
gcc accetta il template precedente se anzichč "class*" si usa "class T*",
ma sembra incapace di instanziarlo.
 
Max

};
perché usi (void) nella lista di argomenti? credevo che lo standard
richiedesse le () vuote, ma se tu fai così ci sarà un motivo...
Anto

ma sembra incapace di instanziarlo.
La prova con g++ l'avevo fatta anche io. Quando scrivi:
   template<class T *p>
   ...
g++ si aspetta un puntatore a una istanza di una classe *concreta*
di nome T. Ho gia' avuto problemi in passato su questa cosa...
Penso che se in C++ si fossero scelti nomi speciali per i tipi
ci sarebbero state molte meno grane sul parsing dei template
(qualcosa come... template<@X *p>)
Ho anche provato (giusto per provare) a scrivere cose come...
  template<class T>
  template<T *p>
  void call()
  {
    p->call();
  }
E il sorprendente risultato e'...
  conv.cpp: In function `int main()':
  conv.cpp:31: Internal compiler error.
  conv.cpp:31: Please submit a full bug report ...


 (qualcosa come... template<@X *p>)
Non è un'idea malvagia. Peraltro, se i nomi dei tipi fossero
caratterizzati in qualche modo, non sarebbe mai necessario il ricorso alla
keyword "typename".
A proposito (beh, non c'entra necessariamente, ma era un po' che volevo
chiederlo): entrambi i compilatori che mi capita di usare (MSVC e gcc)
accettano '$' come carattere valido in un nome di tipo o di variabile. Non
è consentito dallo standard, vero? Si tratta di un'estensione. O no?
Max

è consentito dallo standard, vero? Si tratta di un'estensione. O no?
Non ho una copia dello standard e quindi non lo so.
Spero solo che, nel caso che sia ammesso, non sia illegale che
io prenda a randellate sulla nuca l'autore di un sorgente che
usa pesantemente questa possibilita' nel caso che mai mi
capitasse la necessita' di dover mettere il naso nel suo codice.
Non e' che ti riferisci ai nomi consentiti dal linker per i simboli
esportati dai moduli ? si tratta di qualcosa di molto diverso.
In questo caso e' l'assemblatore/linker che deve poterli gestire e
con il sorgente C++ non hanno a che fare. Ho visto anche io che il
'$' e' un carattere usato spesso per costruire il nome esterno
delle classi e delle funzioni C++.


 esportati dai moduli ?
No, mi riferivo proprio ai nomi usati nel codice.
Questo, ad es., si lascia compilare senza problemi dal gcc:
 struct pippo$topolino {
   void foo$bar () {};
 };
 pippo$topolino  $$$$$minni;
 $$$$$minni.foo$bar () ;
Max

 esportati dai moduli ? si tratta di qualcosa di molto diverso.
Non mi ricordo dove l'ho letto (certamente da qualche parte della sconfinata
rete), anche perchč l'argomento non mi interessava piů di tanto, ma ho letto
appunto che "anticamente" (1700-1750) si accettava il simbolo "$" negli
identificatori, pratica poi (sempre se non mi sbaglio) proibita dallo
Standard.
Chiedo scusa per le informazioni cosě imprecise, ma credo che sia meglio di
niente :-)
Ciao
Domenico Barra

Questo, ad es., si lascia compilare senza problemi dal gcc:
...
BCC55 non e' daccordo:
   Error E2206 test.cpp 5: Illegal character '$' (0x24) in
   function main()
Io come ho gia' detto non possiedo lo standard... ma forse
l'uso di questo carattere risale a quando il C++ non era che
un preprocessore per compilatori C (in modo da semplificarsi
un po' la vita sugli overload).


 è consentito dallo standard, vero? Si tratta di un'estensione. O no?
Lo standard prevede lettera o underscore come carattere iniziale (anche
se l'underscore iniziale e' in molti casi riservato all'implementazione)
e lettera, cifra o underscore per i successivi.
Ciao,
Nicola

keyword "typename".
quando č necessario il ricorso a typename?
Anto
--
Antonio Cuni, 18 anni, studente
Brainbench Certification: 
Master C++: 4.44, higher than 97% of all previous test takers.
Visual Basic 5: 3.06, higher than 77% of all previous test takers.
http://www.brainbench.com/transcript.jsp?pid=1301688

 quando è necessario il ricorso a typename?
Quando la definizione del tipo e' incapsulata all'interno di una classe. Se
io scrivo Myclass::Mytype, l'unico modo per il compilatore di sapere che si
tratta di un tipo (e non di qualcos'altro come una variabile) e' dato
proprio da 'typename'.
Ciao
Domenico Barra

proprio da 'typename'.
non ho capito; da quel che dici tu sembrerebbe se ho
class Myclass {
	public:
	typedef int Mytype;
};
template<class T> MyTemplate { };
non posso istanziare
MyTemplate<MyClass::MyType>;
ho capito bene? credo (spero) proprio di no. me lo puoi spiegare con
un esempio?


 ho capito bene?
No. Nel tuo esempio il compilatore non č tenuto a sapere cos'č
MyClass::MyType nel punto in cui il template viene definito e al momento
della sua instanziore MyClass::MyType č il nome di un tipo la cui
definizione č perfettamente nota.
Considera invece questo:
 template< class T >
 void func( T& v )
 {
    typename T::iterator i = v.begin();
 }
In questo caso il ricorso a typename č indispensabile, giacchč
diversamente il compilatore non avrebbe modo di intuire che "T::iterator"
č il nome di un tipo e non quello di un membro.
Max

 un esempio?
In questo caso, dato che un parametro di un template può essere solo un
tipo, il compilatore dà per scontato che si tratti di un nome di tipo, e
non, ad esempio, di un identificatore di variabile.
Ciao
Domenico Barra