Newsgroup: it.comp.lang.c++
Titolo: Problema di RTTI con il Builder

Questo e' lo pseudo-codice che mi da' problemi:
TClasseBase
{
...
};
TDerivata1 : public TClasseBase
{
__published:
    TEdit *EditUno;
    TEdit *EditDue;
...
};
TDerivata2 : public TClasseBase
{
__published:
    TEdit *EditUno;
    TEdit *EditDue;
...
};
Ora, passo ad una funzione un puntatore alla classe base:
MyFunction(TClasseBase *ptr);
Vorrei impostare da MyFunction i valori di EditUno ed EditDue
rispettivamente di TDerivata1 e TDerivata2 (o meglio, di istanze di
TDerivata1 e TDerivata2), a seconda del valore assunto da ptr.
Esempio; nel mio programma ho dichiarato:
TDerivata1 *deriv1 = new TDerivata1;
TDerivata2 *deriv2 = new TDerivata2;
void AFunction(void)
{
    TClasseBase *base;
    if (...alcune opzioni...) base = deriv1;
    else base = deriv2;
    MyFunction(base);
}
void MyFunction(TClasseBase *ptr)
{
    ptr->EditUno->Text = "Uno";    // qui mi da errore
    ptr->EditDue->Text = "Due";   // dice che EditUno non e' un membro di
TClasseBase
}
Se faccio cosi' funziona:
((TDerivata1 *)prt)->EditUno->Text = "Uno";
ma avendo la necessita' di dover distingure tra TDerivata1 e TDerivata2,
come posso fare senza mettere degli if?
--
Byez
Stefano Tempesta
temp...@tin.it
http://tempesta.freeweb.org

    ptr->EditUno->Text = "Uno";    // qui mi da errore
diventa
        ptr->getEdit(1)->Text = "Uno";
non solo senza errore, ma in uno stile decisamente migliore.
(C'e` ancora spazio per ulteriori miglioramenti, ma mi rendo
conto che qui si comincia a scontrarsi con le estensioni non
standard del Builder al C++, per cui quella "assegnazione a
membro-dati", Text = "Uno", puo` in realta` nascondere altre
cose; almeno, cerchiamo di mantenere come C++ pulito ed
efficace quella parte del codice che non va a sbattere contro
queste estensioni proprietarie, eh!).
Il polimorfismo e` potente, pulito, rapido, e sicuro.  Il "codice
cliente" (la tua MyFunction) NON DEVE preoccuparsi di come
le cose siano implementate da varie possibili sottoclassi
concrete di TClasseBase; deve SOLO sapere del "protocollo
di accesso", cioe' dell'esistenza di una funzione virtuale
"getEdit", che ogni sottoclasse concreta implementera` come
piu` le fara` comodo, e che comunque e` IL modo in cui si
ottiene l'accesso all'opportuno Edit.  Beneficio collaterale
tutt'altro che trascurabile, evitiamo di accedere direttamente
a membri-dati pubblicamente esposti: l'accesso tramite
funzioni (specialmente funzioni virtuali, come il polimorfismo
richiede) e` piu` "pulito", piu` solido, piu` efficace, e presenta
ogni sorta di vantaggi.
Tutto cio` detto, e pur nella "sostanziale certezza" che l'approccio
polimorfico sia sicuramente quello piu` appropriato al tuo problema,
aggiungo per completezza che il C++ fornisce anche, per quel
caso-su-mille (o meno) in cui il polimorfismo non e` perfetto, la
possibilita` di accedere anche alla "identificazione dei tipi a
runtime" (RTTI, come da subject); il costrutto principale, che
copre il 99%+ delle esigenze (di questi casi-su-mille o meno,
*ricordiamolo*), e` dynamic_cast.
Ti consiglio, tuttavia, di essere veramente SICURO, prima di
ricorrere a dynamic_cast, che un dato problema NON POSSA
essere risolto in modo ottimale con altre tecniche (soprattutto
il polimorfismo); vedrai, nella STRAGRANDE maggioranza dei
casi, che non c'e` una buona ragione per pagare i tanti costi
che dynamic_cast ti impone (esecuzione assai piu` lenta, rispetto
alla risoluzione polimorfica di chiamate virtuali che e` rapidissima;
rischio molto maggiore di errori run-time che invece si sarebbero
potuti, ad assai minor costo, diagnosticare compile-time o link-time;
necessita` di PESANTI e INVASIVI interventi di manutenzione se
cambia l'implementazione [aggiungendo altre classi derivate], a
fronte del meraviglioso "decoupling" interfaccia/implementazione
che il polimorfismo consente; e cosi` via).
Alex

 come posso fare senza mettere degli if?
Volendo determinare a runtime il tipo di un oggetto a partire da un puntatore a
una classe base, si usa in genere il dynamic_cast, ma da quanto scrivi sembra
che tutto il problema derivi dal fatto che non hai applicato uno dei principi
base della programmazione object oriented, quello di mettere tutti i membri di
uso generale (cioè utilizzabili da tutte le classi derivate) nella classe base e
solo quelli di uso "specializzato" nelle classi derivate.
Quello che hai fatto equivale a scrivere:
----------------------------------------------------
class Persona
{
   .......
};
class Impiegato : public Persona
{
   .......
   string Nome;
   string Cognome;
   string Qualifica;
   .......
};
class Studente : public Persona
{
   .......
   string Nome;
   string Cognome;
   string Matricola;
   .......
};
---------------------------------------------------
anzichè
---------------------------------------------------
class Persona
{
   .......
   string Nome;
   string Cognome;
   .......
};
class Impiegato : public Persona
{
   .......
   string Qualifica;
   .......
};
class Studente : public Persona
{
   .......
   string Matricola;
   .......
};
---------------------------------------------------
Poichè non sono propenso a pensare che
l'errore sia semplicemente imputabile a poca confidenza con i principi base
della
programmazione a oggetti, e visto che usi il C++Builder, immagino che sia
attribuibile all'aver seguito, senza starci troppo a riflettere, la "via più
breve" messa a disposizione dall'ambiente RAD. 
Se la mia supposizione corrisponde a verità, il tuo caso esemplifica bene i
rischi che questi ambienti comportano soprattutto per chi li affronta senza
molta esperienza di programmazione alle spalle.
Quello che suppongo è che tu abbia creato due Form con caratteristiche in parte
simili, ma con alcune differenze, utilizzando il metodo più comune in
C++Builder, cioè la voce di menù "New Form", col che ti sei ritrovato con due
oggetti estranei tra di loro (anche se entrambi derivati da TForm), pur avendo
contenuti e (immagino) funzionalità simili.
Probabilmente se avessi costruito i tuoi Forms da codice, non avresti fatto un
errore simile, ma avresti messo i membri condivisi dai due Form in una classe
base.
Per fortuna BCB mette a disposizione uno strumento (che sospetto sia abbastanza
poco utilizzato, a dispetto della sua indubbia utilità) che permette di usare
l'ereditarietà con i Form (o altri componenti "compositi" che utilizzano i files
DFM) anche direttamente da IDE. 
Si tratta di un meccanismo di ereditarietà delle "form definitions" associate al
componente (quelle che stanno nei files DFM, qualcosa di simile alle risorse
Windows).
In pratica, se tu crei un Form che chiamiamo Base, ci metti i componenti e il
codice che è condiviso dai due Form che vuoi realizzare, poi dal menù scegli la
voce New..., vai alla pagina col nome del progetto, selezioni il Form Base e
scegli l'opzione Inherit, l'IDE ti crea un nuovo Form che non soltanto è
implementato come classe derivata da Base, ma le cui "definizioni" (DFM) sono
legate a quelle di Base da un meccanismo di ereditarietà simile a quello delle
classi. Cioè se lasci i componenti ereditati così come sono, conserveranno
aspetto e comportamento definiti in Base. Se vuoi cambiare qualcosa, non devi
fare altro che modificare le proprietà (ad esempio posizione, dimensioni, colori
ecc.) nel Form derivato, e nel File DFM saranno incluse solo le modifiche
rispetto a Base, mentre tutte le altre proprietà continueranno a essere
ereditate. Lo stesso per gli eventi: se vuoi, puoi utilizzare i gestori di Base,
se no puoi riassegnare il gestore e riscrivere il codice, eventualmente
richiamando anche il codice del gestore di Base.
E naturalmente puoi aggiungere componenti e codice, mentre non puoi eliminare
componenti ereditati.
Ciao
Paolo

 base della programmazione object oriented
Ah beh, allora prenditela con quelli della Inprise, o meglio della QuSoft,
visto che il mio problema e' legato a classi del QuickReport fornito con il
C++Builder 3 :-)))
In realta' nessun "codice di programmazione" e' stato violato! Le cose
stanno cosi':
ho due form del quickreport sul quale ci sono diversi oggetti con lo stesso
nome
TQRForm1 *QRForm1;
TQRForm2 *QRForm2;
sia QRForm1 che QRForm2 hanno un oggetto TQRLabel1
Come faccio a impostare tale oggetto (comune a tutti i due form) a seconda
del tipo di form che apro (passando alla funzione dedita all'impostazione
della label un puntatore alla classe base di TQRForm1 e TQRForm2, ossia
TQuickRep)?
Al momento ho risolto cosi':
if (ptr == QRForm1)
    (QRForm1 *)ptr->Label1->Caption = "xxx";
else if (ptr == QRForm2)
    (QRForm2 *)ptr->Label1->Caption = "xxx";
dove ptr e' il parametro della mia funzione passato come TQuickRep *.


 Byez
Byez???
Ciao
Paolo

non c'č nulla che ti impedisca di crearti un Report base A e due Report
derivati B e C.
Ok, ma la disposizione dei campi sul report? Dovrei farla da codice...


 
Come dicevo, nei form derivati puoi cambiare anche A DESIGN TIME alcune
proprieta`
lasciandone inalterate altre, cosi` come puoi cambiare gestori di eventi, e nel
file DFM del form derivato verranno conservate le informazioni su queste
modifiche. Quindi puoi avere un form base (che funge da base "astratta") con due
form derivati, ambedue diversi per qualche aspetto (ad esempio la posizione dei
componenti) da quello base. Oppure potresti avere un form base "concreto", ossia
effettivamente usato, e uno derivato che differisce dall'altro per qualche
aspetto. Quale sia la soluzione piu` appropriata dipende naturalmente dal
contesto. La procedura e` quella che ho descritto nei messaggi precedenti.
Saluti
Paolo

>La procedura e` quella che ho descritto nei messaggi precedenti.
Grazie, ho fatto proprio cosi'.



Stefano Tempesta wrote:

Questo e' lo pseudo-codice che mi da' problemi:


TClasseBase

{

...

};


TDerivata1 : public TClasseBase

{

__published:

    TEdit *EditUno;

    TEdit *EditDue;

...

};


TDerivata2 : public TClasseBase

{

__published:

    TEdit *EditUno;

    TEdit *EditDue;

...

};


Ora, passo ad una funzione un puntatore alla classe base:

MyFunction(TClasseBase *ptr);


Vorrei impostare da MyFunction i valori di EditUno ed EditDue

rispettivamente di TDerivata1 e TDerivata2 (o meglio, di istanze di

TDerivata1 e TDerivata2), a seconda del valore assunto da ptr.

Esempio; nel mio programma ho dichiarato:


TDerivata1 *deriv1 = new TDerivata1;

TDerivata2 *deriv2 = new TDerivata2;


void AFunction(void)

{

    TClasseBase *base;


    if (...alcune opzioni...) base = deriv1;

    else base = deriv2;


    MyFunction(base);

}


void MyFunction(TClasseBase *ptr)

{

    ptr->EditUno->Text = "Uno";    //
qui mi da errore

    ptr->EditDue->Text = "Due";   // dice
che EditUno non e' un membro di

TClasseBase

}


Se faccio cosi' funziona:

((TDerivata1 *)prt)->EditUno->Text = "Uno";


ma avendo la necessita' di dover distingure tra TDerivata1 e TDerivata2,

come posso fare senza mettere degli if?


--

Byez


Stefano Tempesta

temp...@tin.it


http://tempesta.freeweb.org

 Puoi usare un metodo polimorfico dichiarato nella classe base virtual
ad esempio


virtual TClasseBase::update( parametri) = 0;


e poi ridefinire il metodo nelle due classi derivate in modo da aggiornare
gli attributi specifici


virtual TClasseBase::update( parametri)

{

     EditUno = ...;

     EditDue = ...;

}


Tuttavia visto che gli attributi EditUno e EditDue sono comuni alle
due classi derivate, dovresti chiederti se non sarebbe piu' giusto definire
tali attributi nella classe base. La risposta e' si nel caso tali attributi
rappresentino le stesse prpprieta' nelle due sottoclassi.

In tal caso il problema non si presenta piu', in quanto col puntatore
alla classe base non tenti piu' di accedere ad attributi specifici delle
sottoclassi.

 

 

-- 
***********************************
Luca Chiesa   
Comune di Torino   
mailto:tsv...@comune.torino.it

***********************************