Newsgroup: it.comp.lang.c++
Titolo: riflessività in c++

Ciao a tutto il newsgroup. Eccomi qui di nuovo con una delle domande che 
dimostrano che sono più un programmatore Java che C++.
La situazione è la seguente: ho un container con dentro degli oggetti 
(in realà sono dei puntatori a quegli oggetti), tutti gli oggetti 
contenuti ereditano da una stessa classe (considerate il classico 
esempio delle *shape*). Una volta che ho estratto un puntatore da questo 
container, ho modo di capire a che sottoclasse punta e chiamare i metodi 
della sotto classe? Insomma posso determinare a runtime la classe di 
appartenenza di un oggetto? se si, come?
Grazie mille,
	Matteo

M.Corno, hai turbato la mia meditazione scrivendo questo:

Forse potresti riuscirci con le funzioni virtuali, ma ho studiato molto poco
questo argomento (so a malapena che esistono), e potrei avere sparato una
cazzata immane.
--
Lupin85©
FILSer #2 - Founder di #fils
"It is not important what you believe, only that you believe"

 "It is not important what you believe, only that you believe"
La cosa potrebbe funzionare, non č quello che cercavo e forse per questo 
non l'ho trovata. Risolve il mio problema specifico ma non la domanda in 
generale.
Grazie,
Matteo

 ha scritto nel messaggio
news:YQrc9.48806$pX1.1...@news2.tin.it...

Mmmm l'ereditarieta' servirebbe proprio per svincolare il programmatore dal
sapere a quale delle sottoclassi punti un puntatore :)
Cioe' nel seguente esempio:
class Base {
public:
    Base(){};
    ~Base(){};
   virtual int VirtualBaseMethod()  = 0;
};
class Derived_A : public Base{
private:
   int Derived_A_Data ;
public:
    Derived_A() : Base( ){};
    ~Derived_A(){};
   virtual int VirtualBaseMethod()
   {
       return Derived_A_Data ;
   };
  void Derived_A_Member_Func( const char *s )
   {
        /*
             ....
      */
   };
};
class Derived_B : public Base{
private:
   int Derived_B_Data ;
public:
    Derived_B() : Base( ){};
    ~Derived_B(){};
   virtual int VirtualBaseMethod()
   {
       return Derived_B_Data;
   };
  void Derived_B_Member_Func( int A )
   {
       /*
             ....
      */
   };
};
int main(int argc , char* argc[] )
{
    Base *p;
    Derived_A dA,*p_dA;
    Derived_B dB, *p_dB;
   /*  */
   p = &dA;
   p->VirtualBaseMethod(); //Ok
   p= &dB;
   p->VirtualBaseMethod(); //Ok
   /* Downcast */
   p_dB = static_cast<Derived_B*>(p);
   /* Qui non ci sono errori run time */
   p_dB->Derived_B_Member_Func(10);
   /*  Perche' p puntava effettivamente a un oggetto di tipo B ma se ...*/
   p_dA = static_cast<Derived_A*>(p);
   p_dA->Derived_A_Member_Func("    ");// genera un eccezione perche' p
punta a un oggetto di tipo B
                                                                   // e B
non ha come funzione membro Derived_A_Member_Func
};
Tutto sto spiegone per dire che secondo me il downcasting nel tuo caso non
e' la cosa migliore(non che sia sbagliato,
ma puo' incasinare parecchio :P).
Quando inserisci  i puntatori alle sottoclassi in un container che contiene
puntatori di un tipo base di quella classe
sfrutti il polimorfismo, quindi a questo punto converrebbe sfruttarlo come
si deve e utilizzare delle funzioni virtuali
per creare nella classe base un interfaccia comune a tutte le classi
derivate.
Cmq per ricavarti il tipo di un oggetto a run time puoi usare:
const type_info& t = typeid(*var);
ti ritorna una struttura con
t.name
() : il nome della classe in formato leggibile(come hai compilato)
t.rawname() : il nome decorato(cioe' con tutti i modificatori aggiunti da
compilatore e linker)
Il problema e' che cmq typeid mi pare che non sia proprio standard...
ad esempio utilizzando le classi dell'esempio sopra
Base *p;
p = new Derived_A;
typedid(*p).name sotto MSVC C++ ritorna "class Derived_A" , sotto il Borland
C++ Builder mi pare ritorni solo il nome della classe quindi "Derived_A".
Io ti consiglio di definire nella classe base , un' interfaccia di funzioni
virtuali (anche astratte, anzi meglio ancora) che le classi derivate devo
implementare.
Ti assicuro che(almeno per quanto mi riguarda) il codice rimane molto piu'
pulito e strutturato meglio.

A me il gpp ritorna
9Derived_A  // 9 è la lunghezza del nome
e rawname() non la trova, mi da errore.
ciao
--
Luigi
--
void f (OS x) {while (x.getbugs() > 0); destroy(earth);};
f (windows); // Meno male!

 ha scritto nel messaggio
news:Eluc9.136225$n04.3...@twister2.libero.it...

Ho riletto mi sono espresso male.
e' la struttura type_info che e' compiler specific, perche' ritorna delle
informazioni che il COMPILATORE crea
al momento del parsing del codice.
Quindi dipende dall'implementazione del compilatore.

 classe di appartenenza di un oggetto? se si, come?
	L'argomento in generale si chiama RTTI (run time type identification),
e oltre al gia' menzionato typeid contiene anche e soprattutto il
dynamic_cast<T*>(p). "dynamic_cast<T*>(p)" e' un operatore con le
sembianze di una funzione template, che ritorna un puntatore alla classe
T se p e' di quel tipo, 0 altrimenti o se p == 0. La limitazione e' che
T deve essere un tipo polimorfo.
	Questo operatore in certi casi *serve*, ma in generale non nel caso di
un container pieno di puntatori. In quel caso una buona progettazione
finisce sempre per sfruttare il polimorfismo.
_dave_

 della sotto classe?
Risposta breve:
Puoi capirlo sfruttando l'RTTI (dynamic_cast<>, typeid, etc; vedi manuale).
Risposta (abbastanza) lunga:
La necessità di conoscere il tipo effettivo di un oggetto può indicare un
errore o una carenza progettuale. Dopo tutto, il meccanismo delle chiamate
virtuali serve proprio per operare sugli oggetti *senza* conoscerne il tipo
effettivo. L'uso massiccio di dynamic_cast<> può rendere disagevole la
manutenzione e lo sviluppo, soprattutto quando devono essere introdotti nuovi
sottotipi.
In questi casi, talvolta può essere sufficiente ampliare l'interfaccia della
classe base e spostare nelle implementazioni delle classi concrete quelle
operazioni che hanno la necessità di conoscere il tipo effettivo, o,
perlomeno, avere accesso ad un'interfaccia più specializzata.
A volte, questo è molto scomodo. Se queste operazioni sono numerose e possono
aumentare con il tempo si rischia di dover modificare in continuazione
l'interfaccia base.
Una soluzione elegante, applicabile in molti di quei casi in cui si è tentati
di usare l'RTTI, è rappresentata da un pattern denominato "visitor". Illustro
l'idea centrale con un esempio. Per approfondire basta fare una ricerca su
Google, o leggersi Design Pattern.
Supponiamo che la nostra classe shape abbia due sottoclassi (anche'esse
astratte) di nome triangle e circle:
  class triangle: public shape
  {  // vari metodi astratti specifici di triangle
  };
  class circle: public shape
  {  // vari metodi astratti specifici di circle
  };
Ora, diciamo che abbiamo un vettore di puntatori a shape e vogliamo scorrerlo
per compiere con ciascuno di essi qualche operazione che richieda la
conoscenza di dettagli specifici dell'interfaccia circle o triangle (ad
esempio: calcolarne l'area, disegnarlo, etc). Come ho detto, potremmo per
ognuna di queste operazioni estendere shape con un metodo apposito e usare
quello. Ma noi vogliamo mantenere stabile l'interfaccia di shape. Allora
possiamo procedere così.
Creiamo una classe visitor astratta che abbia un metodo per ognuno dei
sottotipi di shape:
  class shape_visitor
  {   public:
           virtual ~shape_visitor() {}
           virtual void visit_shape( triangle& t ) = 0;
           virtual void visit_shape( circle& c ) = 0;
  };
Aggiungiamo alla classe shape un singolo metodo:
         virtual void accept( shape_visitor& ) = 0;
(Nota che per aggiungere questo metodo non occorre che la definizione di
shape_visitor sia visibile a chi include la definizione di shape: possiamo
usare una forward declaration.)
Le classi concrete derivate da triangle o circle dovranno implementare
accept() in questo modo:
        void accept( shape_visitor& v )
         {          v.visit_shape( *this );
          }
Ora, per fare un esempio, diciamo che vogliamo _disegnare_ tutti gli oggetti
del vettore.
Creiamo una classe concreta shape_drawer derivata da shape_visitor:
  class shape_drawer: public shape_visitor
  {   public:
           void visit_shape( triangle& t ) { /* disegno un triangle */ }
           void visit_shape( circle& c )   { /* disegno un circle */  }
  };
A questo punto, se aDrawer è un'instanza della classe shape_drawer, per ognuno
dei nostri puntatori a shape dovremmo soltanto invocare accept() in questo
modo:
      shape* ptr;
      /* estraggo ptr dal vettore */
      ptr->accept( aDrawer );
L'appropriato overload di visit_shape verrà chiamato (in virtù
dell'implementazione di accept(), che abbiamo visto) e l'oggetto aDrawer
eseguirà l'operazione potendo contare sull'interfaccia completa di triangle o
di circle.
Vantaggi:
Per ogni tipo di operazione che richieda l'accesso all'interfacca triangle o
circle è sufficiente implementare un appropriato shape_visitor. Le varie
implementazioni di quella che è concettualmente la stessa operazione sono
raccolte nella stessa classe visitor e non distribuite nelle classi derivate
da triangle o circle.
Svantaggi:
Se si vuole aggiungere nuovi sottotipi a shade, occorre modificare
l'interfaccia di shade_visitor e aggiungere altri overload di visit_shape().
Max