Newsgroup: it.comp.lang.c++
Titolo: Come si usa RTTI?

Salve a tutti.
Qualcuno mi può dare delle indicazioni sui possibili utilizzi della
funzione typeid()?
Nel mio caso specifico ho bisogno di effettuare una scelta in base al
tipo di un certo oggetto, del tipo:
if (oggetto.tipo == A) "fai questo";
  else (oggetto.tipo == B) "fai quest'altro";
    else "fai quest'altro ancora";
Come posso usare RTTI per avere oggetto.tipo?
Devo specificare qualche flag di compilazione per poter usare RTTI o le
informazione vengono inserite per default?
Vi sarei grato anche se poteste darmi altri esempi di utilizzo (per
l'amor di Dio, esempi semplicissimi).
Dove si può trovare del materiale didattico sull'argomento? Le
documentazioni sono piuttosto scarne.
Grazie,
Marcello.

Sto usando come compilatore il gcc ver2.91.66.

 documentazioni sono piuttosto scarne.
L'RTTI e` considerata (a pieno titolo) "tema molto avanzato", e cio`
soprattutto per gli orrendi errori d'impostazione nei quali puo`
ahime` facilmente indurre quasi qualsiasi programmatore.
Nel 99% del tuo codice non dovrebbe servirti alcuna RTTI.  In un
buon 99% del restante 1% dei casi, dynamic_cast fa esattamente
quel che ti serve.  L'uso diretto di typeid e di funzioni della
class type_info non e` praticamente mai l'approccio giusto, se
proprio non stai scrivendo codice estremamente peculiare (un intero
framework di persistenza/depersistenza, ad esempio...).
Alex

 Questo costrutto, noto come "typeswitch", e` DELETERIO.
Infatti prima di decidere di adottare definitivamente questo criterio ho
dovuto ripetutamente darmi delle martellate sui denti affrettandomi a
battere il codice il più presto possibile. :-4
Il mio caso particolare era questo. Ho una gerarchia di questo tipo:
      A
      |
      V
  +---+---+
  |   |   |
  V   V   V
  |   |   |
  A1  A2  A3
  |   |   |
  V   V   V
  |   |   |
  AB1 AB2 AB3-<-+----B
  ^   ^   ^     |
  +---+---+-----+ 
Nella mia applicazione degli oggetti A1, A2 e A3 disposti a caso
all'interno di una lista. Su di essi effettuo un certo numero di
elaborazioni. Questi oggetti devono poi essere trasmessi sulla rete. Ho
creato dunque una classe base dai quali discendono gli oggetti
trasferibili sulla rete (fondamentalmente fa streaming ed aggiunge
informazioni di controllo).
Ho quindi la necessità di trasformare la lista di oggetti semplici in
una lista di oggetti specializzati (ogni A1 in AB1, ogni A2 in Ab2,
...etc).
La possibilità di dotare la classe A di un metodo virtuale specializza()
non mi piace perché questo vorrebbe dire che ogni oggetto A deve
conoscere la sua versione specializzata. Ho così optato per il
type-switching. Non mantengo l'"open-closed" ma almeno le modifiche da
fare nel caso di estensione sono totalmente localizzate.
Naturalmente accetto molto volentieri consigli alternativi: il programma
funziona (avevo già scoperto il dynamic_cast<>() ), ma io voglio
imparare. :)
Grazie,
Marcello.

 conoscere la sua versione specializzata. 
                   ^^^^^^^^^^^^^^^^^^^^^^
Se scrivi
	class A {
		public:
			virtual void specializza() = 0;
	};
	class B : public A {
		public: 
			void specializza() { ...fa qualcosa ... }
	};
in che senso A dovrebbe conoscere la "sua versione specializzata"???
Fausto

 in che senso A dovrebbe conoscere la "sua versione specializzata"???
Nella tua gerarchia chi si specializza: A in B oppure B in
qualcos'altro?
Nel primo caso A.specializza() non può essere astratto;
nel secondo caso B deve conoscere la classe in cui va a specializzarsi
(quantomeno per poterne chiamare il costruttore) che è una sua
discendente. E questo lo ritengo un po' sporco.
Ciao,
Marcello.


                                   ^^^^^^^^^^^^^
Scusami ma sono troppo curioso... ...cosa sarebbe "sporco"???
Inoltre scrivere
	B b;
	A& a = b;
	a.specializza();
per te sarebbe "sporco"???
Fausto

 imparare. :)
La classe type_info ha un metodo before(), che fornice un
arbitrario ordinamento, proprio allo specifico scopo di
potere essere usata come chiave in una std::map.  (Dovrai,
credo, specializzare std::less<>, ma e` banale farlo).
Questo e` un uso di RTTI assai preferibile rispetto al
typeswitch.  Per intenderci:
typedef B* (*A_to_B_mapfun_t)(A*);
std::map<type_info, A_to_B_mapfun_t> conversori;
B* converti_un_A(A* pA)
{
    pf = conversori[typeid(*pA)];
    if(!pf) return 0; // o throw, ecc ecc
    return (*pf)(pA);
}
Aggiungendo, anche dinamicamente, una qualche
nuova classe, che so, A7, e il relativo AB7,
basta che quest'ultima registri una propria
(static!) funzione di conversione (e` un caso
di idioma/pattern 'factory') nella mappa detta
'conversori' (e cosi` pure puo` togliersene,
ecc ecc).  Naturalmente puoi incapsulare il tutto
piu` elegantemente (con un oggetto globale
"conversore" che mantiene la mappa, metodi
di registrazione e rimozione dalla mappa, e
la funzione converti_un_A, ecc) ma la sostanza
e` questa.
Un altro packaging della stessa idea ricorre pur
sempre alla virtual B* specializza() in A, ma la
implementazione concreta della specializza() in
ogni An non ha bisogno di conoscere nulla, altro
che il fatto di delegare alla converti_un_A.  Uno
sviluppo di questo tipo di packaging e` la pattern
'acyclic visitor' di R. Martin (da NON confondere
con la visitor originale, che non e` dinamica, NON
e` aciclica, e NON fa al caso tuo!-).
Alex

 per te sarebbe "sporco"???
Io non ho capito bene questo pezzo di codice. Se funziona sono contento,
ma non riesco a capire come potrebbe. Sempre senza voler rompere le
sfere (che ereditano dirrettamente dalla classe astratta "palla") vorrei
enfatizzare una cosa: secondo me, che potrei anche stare clamorosamente
prendendo un abbaglio, il metodo specializza deve avere un prototipo del
tipo:
  virtual B& A::specializza() const = 0;
in modo tale da restituire il riferimento ad una _nuova_ istanza di se
stesso, ma specializzata, naturalmente dopo averla costruita (e per
costruirla deve conoscerla).
Il programma quindi sarebbe del tipo:
  A1 a;
  ...(tutte le elaborazioni su a)
  B& b;
  b = a.specializza();  //Qui "a" che è di tipo A1 crea un'istanza di
tipo AB1
  b.mandasullarete();
Questo però ha il difetto che A1 deve conoscere AB1 (la relazione di
inclusione indesiderata di prima).
Sperando di stare sbagliando e che la soluzione sia davanti a me a
guardarmi con aria di compassione...
vi saluto ancora e vi ringrazio tutti per i consigli forniti...
Marcello.

 e` aciclica, e NON fa al caso tuo!-).
Questa già è più comprensibile, ma mi costringerebbe a creare quella
relazione di inclusione circolare tra units che, non so perché, volevo
evitare. :)
Salutoni,
Marcello.

 dell'ereditarietą possono essere astratte?
                                   ^^^^^^^^
Oooppss. CONCRETE! :)

 Fausto
se utilizzo una funzione globale Specializza(class A&  tipo_classe){
tipo_classe.specializza(){..fai qualcosa...}
io passo alla funzione l'oggetto che mi serve che esegue esattamente il
comando che voglio senbza conoscere altro
ciao Nicola

 evitare. :)
La relazione di dipendenza ciclica fra componenti (che penso
siano quelli che tu chiami units) e` una iattura e fai benissimo
a sforzarti al massimo per evitarla.  Ma quello che forse non
vedi e` che, avendo introdotto una interfaccia astratta, noi
qui abbiamo implicitamente adottato la dependency inversion
alla Martin, e quindi non c'e` piu` nessuna ciclicita` in gioco.
Considera ad esempio...:
In "conversor.h":
class A;    // forward-declaration: "esiste una classe A"
class B;    // forward-declaration: "esiste una classe B"
B* converti_un_A(const A*);
Un #include di questo header file non aggiunge nessuna
dipendenza a nessuno, altro che sull'esistenza e i nomi
di due classi A e B, "legame" cosi` debole da non dare
alcun fastidio.
In "A.h":
#include "conversor.h"
class A {
public:
    virtual B* specializza() const = 0;
    // ecc ecc
};
In, che so, A3.h:
#include "A.h"
class A3: public A {
public:
    virtual B* specializza() const {
        return converti_un_A(this);
    }
};
Nota che il polimorfismo lo stiamo usando solo per
futura flessibilita`: eventuali future sottoclassi
concrete di A che abbiano altri approcci per
"specializzarsi".  Potremmo anche fare non-pura la
A::specializza, visto che il "default" e` sempre
quello di delegare alla converti_un_A...!
Se scegliamo questa INTERFACCIA, resta solo il
"dettaglio" (non proprio un dettaglio, ma certo
MOLTO meno importante dell'avere progettato bene
l'interfaccia, sulla quale ci affidiamo per la
stabilita`...!) di come implementare la funzione
converti_un_A.  E` qui, nella implementazione, che
l'uso di una std::map di funzioni specifiche di
conversione, con il typeid come chiave della mappa,
e` probabilmente l'approccio piu` fruttuoso.
Una tipica sottoclasse concreta di B deve sapersi
costruire a partire dallo specifico sottotipo di A
a cui corrisponde, quindi la dipendenza B3 -> A3,
per esempio, esiste e deve esistere.  Ma non si e`
affatto introdotta nessuna dipendenza ciclica, e
specificamente non vi e` dipendenza A3 -> B3 e cosi`
via.  A3 (ecc) dipendono solo dall'astrazione
"converti-un-A" (NON dall'implementazione della
stessa...!), e questa a sua volta puo` essere resa
non-dipendente dalle Bx in termini implementativi
usando il concetto di mappa e di iscrizione (e
"ritiro dell'iscrizione") di specifici conversori
dalla mappa stessa.
Facciamo un esempio senza std::map, che forse troverai
piu` facile capire.  Se il numero di tipi in gioco
e` modesto, una ricerca lineare puo` essere del tutto
accettabile: la converti_un_A deve allora solo
conoscere una certa lista di possibili-conversori,
ciascuno dei quali puo` provare a convertire un
dato A tornando un'indicazione di fallimento (visto
che torniamo puntatori il ritorno di puntatore nullo
appare come il modo naturale di rappresentare "non
riesco a convertire questo robo").  E quindi un
semplice ciclo sulla lista sino a che uno degli
specifici possibili-conversori non ha successo, o,
se tutti falliscono, alla fine della lista (con
ritorno di un'indicazione complessiva di non-successo).
Alla lista di possibili conversori le varie Bx possono
dunque "iscrivere" quelli di loro interesse.  Se
non interessa _togliere_ dinamicamente dalla lista,
potresti banalmente fare:
in "conversor_impl.h":
    typedef B* (*AtoB_p)(const A*);
    extern std::list<AtoB_p> lalistona;
    inline void aggiungi(AtoB_p p) {
        lalistona.push_back(p);
    }
in "conversor.cpp":
    std::list<AtoB_p> lalistona;
    B* converti_un_A(const A* pA) {
        std::list<AtoB_p>::iterator it;
        B* result = 0;
        for(it=lalistona.begin();
            (!result) && it!=lalistona.end();
            ++it)
            result = (**it)(pA);
        return result;
    }
Un tipico Bx, che so, "B3.cpp":
    #include "A3.h"
    class B3: public B, public A3 {
    public:
        B3(const A3& a3): A3(a3) {}
        static B* prova(const A* pa) {
            const A3* pa3=dynamic_cast<const A3*>(pa);
            if(pa3) return new B3(*pa3);
            else return 0;
        }
        // ecc ecc
    }
con una chiamata "aggiungi(B3::prova)"
come "istallazione" della classe B3 nel
sottosistema in gioco.
Se la questione e` in effetti cosi` pura
e semplice, puoi tra l'altro ottenere una
enorme quantita` di riuso di codice solo
facendo della "prova" una funzione template,
ma sono dettagli.  Il discorso complessivo
e` chiaro...?
Alex

 Il discorso complessivo e` chiaro...?
Lo è stato solo dopo essermi stampato il tuo post ed averlo attentamente
letto più volte.
Grazie.
Marcello.

 letto più volte.
Spiacente per il consumo di carta, ma non sono temi facilissimi
da esporre; sono contento che comunque alla fine la chiarezza
sia arrivata.
Alex


E quindi cosa significa quello che asserisci????
Fausto

   b = a.specializza();  //Qui "a" che è di tipo A1 crea un'istanza di
Forse volevi scrivere   B& b = a.specializza();
Fausto