Newsgroup: it.comp.lang.c++
Titolo: Suggerimento per "stati" di un oggetto

Vorrei porre un quesito, sperando di trarre suggerimenti utili da,
eventualmente, applicare ad un mio programma.
Non si tratta di un problema di "come fare" una cosa, ma solo di "come
sarebbe meglio fare", dato che una implementazione c'è già, abbastanza
efficente e funzionale, solo che per ora è poco OO.
Passo subito ad esporre il problema, prima di perdermi in inutili discorsi
introduttivi:
Ho necessità di implementare una certa classe, che può avere "aspetti"
differenti, e la differrenza fondamentale è costituita da una funzione (e la
sua derivata) che viene elaborata.
In codice:
class A{
...
double funzioneA (double);
}
class B{
...
double funzioneB (double);
}
class C{
...
double funzioneC (double);
}
...
-------------------------------------
Una soluzione "OO" potrebbe essere quella di avere una classe base e
derivare da quella le altre, specializzando i 2 metodi che mi interessano.
Sfortunatamente, voglio che queste funzioni, che definiscono il
comportamento della classe, possano essere cambiati dinamicamente, e per
motivi pratici non mi è possibile effettuare questo cambiamento
semplicemente deallocando la classe e riallocandola con una classe
differente.
Queste classi sono utilizzate tramite un array di array, e sarebbe oltremodo
scomodo dover andare a prendere l'elemento dell'array, deallocarlo, creare
una nuova classe e assegnarla al vettore. Sarebbe fattibile, ma vorrei
evitarlo.
Attualmente ho implementato la cosa inserendo una proprietà (int tipo), e a
questo punto ho una sola classe, che a seconda del valore di questa
prorpietà esegue una funzione piuttosto che l'altra.
In codice:
class Unica{
int tipo;
...
double funzione (double valore)    // l'implementazione è qui solo in questo
post per chiarezza
    {
        switch (tipo){
            ...
        }
    }
}
-------------------------------
Ovviamente dal punto di vista delle prestazioni, questa credo sia la
soluzione più "performante". Dal punto di vista OO invece è uno schifo: le
classi sono differenti sotto ogni punto di vista, essendo entità
profondamente diverse a seconda della funzione da elaborare, e la proprietà
aggiunta non ha alcun riscontro nell'entità che la classe dovrebbe
rappresentare. In pratica si torna alla vecchia "programmazione
parametrica".
In questo aspetto del progetto, le prestazioni non sono un vincolo troppo
stretto, per cui vorrei cercare una soluzione più OO, anche per studiare
nuove soluzioni, e quindi imparare qualcosa di nuovo. Ovviamente non vorrei
qualcosa che fosse *troppo* penalizzante dal punto di vista delle
prestazioni; anche se non è un problema in questo caso, potrebbe esserlo in
altri. Diciamo una buona via di mezzo.
L'unica cosa che mi è venuta in mente è questa:
avere sempre una sola classe, ma "incapsulare" le 2 funzioni in altre
classi, ed accedervi tramite una proprietà che altro non sarebbe che un
puntatore alla classe Funzione.
In codice:
class Unica{
...
OggettoFunzione *calcola;
double funzione(double valore)
    {
        return calcola->funzione(valore);    // lasciate perdere che
l'implementazione sia qui e non altrove
    }
}
class OggettoFunzione{
    virtual double funzione (double);
}
class OggettoFunzioneA{
...
double funzione (double);
}
class OggettoFunzioneB{
...
double funzione (double);
}
class OggettoFunzioneC{
...
double funzione (double);
}
-----------------------------
E forse potrebbe anche andare bene.
Mi domando però di quanto scadano le prestazioni: a mio avviso ad ogni
chiamata della funzione (e qui invece le prestazioni sono abbastanza
rilevati), avrei, in più alla soluzione già implementata, un overhead dovuto
all'uso del puntatore, all'uso di una tabella per individuare l'oggetto
derivato (e l'efficenza dipende da come è implementata), e una chiamata di
procedura.
Il tutto per risparmiare un costrutto switch, peraltro molto veloce essendo
basato sul valore di un intero già impostato e facilmente indirizzabile.
Spero di aver esposto bene il dubbio e confido nei vostri suggerimenti.
Anticipo un bel "grazie!" a chi avrà la pazienza anche solo di leggere
questa lunghissima lettera
--
Alessandro Monti

derivare da quella le altre, specializzando i 2 metodi che mi interessano.
<snip...>

Il design che proponi come ultima alternativa è simile per molti versi ad idioma
dei funtori: crei un oggetto che  si comporta come funzione e che, spesso,
contiene proprio solo la funzione che devi chiamare. Le basi su cui fa perno la
soluzione sono l'ereditarietà e il meccanismo di funzioni virtuali. Attraverso
il polimorfismo, chami di volta in volta la versione della funzione che ti
serve. Ovviamente questo ha il costo che ogni soluzione basata su vtable si
porta dietro.
Coplien in "Advanced C++ Programming Styles and Idioms" ne descrive
un'evoluzione che, all'occorrenza, media tra questo design e qualche costrutto
switch sistemato al posto "giusto". Facendo questo, l'autore avverte due
necessità:
1) salvaguardare l'impatto sulle prestazioni causato da un pesante uso di
funzioni virtuali;
2) non esagerare neppure con l'uso degli switch in quanto poco manutenibili,
invitando alla proliferazione di codice hard-coded, impedendo un design
uniforme.
I riferimenti li trovi ai seguenti paragrafi:
5.6 "Function as Objects" (pag 165) - "Functional and Applicative Programming"
(pag 170).
Spero ti possno essere utili.
AB
--
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Andrea Baruzzo
Via Circonvallazione, 26
Muzzana del Turgnano (UD) - Italy
e-mail: 
andrea....@ud.nettuno.it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> Coplien in "Advanced C++ Programming Styles and Idioms" ne descrive
Ti ringrazio del riferimento, che provvederò a leggere.
In soldoni comunque dici che la mia implementazione potrebbe andare bene,
essendo un buon compromesso tra "OO" a tutti i costi e
ragionevolezza/prestazioni del codice, giusto?
--
Alessandro Monti

Alessandro Monti
Quella che si ispira al sudetto idioma (functor idiom)... si, per quanto non è
il tipico design OO che si incontrerà tutti i giorni. D'altronde un idioma è
proprio caratterizzato dall'adozione di uno stile di programmazione che è più
consono a determinate situazioni e meno ad altre. Il difficile dell'idioma in sè
è il non lasciarsi prendere troppo la mano e saper mediare le forze in gioco in
modo da non arrivare ad avere switch case ingestibili oppure vtable grosse come
una casa. Sul testo di Coplien trovi alcuni suggerimenti.
Può essere un'interessante sperimentazione (sia a livello di design, sia a
livello di codifica). Buon lavoro.
Ciao


Ancora grazie.
Tra l'altro ho postato anche a Carlo Pescio (il concorso per il libro, che
poi ho comprato allo SMAU) e ho ottenuto anche da lui dei buonissimi
suggerimenti. Bravo, famoso e anche gentile, bisognerebbe fargli un
monumento!
--
Alessandro Monti

suggerimenti.
Se ti ha dato altri consigli, potresti postare la mail sul ng?
Sempre se e' possibile.
--
  ==o-   | Running | HomePage 
http://pinguin.linuxbox.com
   /\\-\ |  Linux  | 1st email: r.salmaso (at) 
flashnet.it
 0-\_/-0 |   2.2   | 2nd email: pinguin (at) 
pinguin.linuxbox.com

> Se ti ha dato altri consigli, potresti postare la mail sul ng?
Come no.
Pensavo di disturbare, ma su richiesta ecco il corpo della risposta avuta da
Carlo Pescio. (la domanda era uguale a quella già postata, a parte una
diversa introduzione nelle prime righe)
-------
Ci sono almeno 3 soluzioni al problema, a parte lo switch:
1) usare una classe intermedia, come nel tuo esempio.
2) usare un puntatore a member function.
3) cambiare la classe dell'oggetto senza cambiarne l'identita' (e quindi la
posizione in memoria)
Le soluzioni 1) e 2) sono piu' o meno equivalenti dal punto di vista delle
prestazioni. La 2) si presta ad ottimizzazioni compiler-dependent.
Confrontate con lo switch, possono essere piu' lente se il numero di case e'
basso, ma a differenza dello switch hanno overhead costante al crescere dei
case.
La soluzione 3) e' piu' complicata, e l'ho discussa nel #6 di C++ Informer.
Ti ringrazio comunque per la collaborazione, e ti invito a proporre altre
idee in futuro.
Cordiali saluti,
Carlo Pescio
-------
In realtà finora ho mantenuto lo switch, poichè ho trovato altre cose da
ottimizzare che nella prima analisi non erano evidenti, e un po' di bug da
risolvere, ma mi riprometto di fare un po' di esercizio con i vari metodi
suggeritimi appena possibile.
Grazie a tutti!
--
Alessandro Monti