Newsgroup: it.comp.lang.c++
Titolo: cambiare tipo ad una classe

Mettiamo di avere una gerarchia di classi A, B, C,... , tutte con una 
classe base X. Mettiamo che, a parte X, nessuna di queste classi abbia 
dati membro, ma solo funzioni virtuali. In pratica queste classi 
differiscono solo per il loro tipo.
Es:
class X {
   protected:
     int x;
   public:
     virtual void draw();
     virtual void update();
};
class A: public X {
  public:
    virtual void update();
};
class B: public X {
  public:
    virtual void update();
};
Ora vorrei che, ad esempio, il metodo update() dell'oggetto b di tipo B 
modificasse (in alcune circostanze) il tipo dell'oggetto b da B ad A.
In pratica questo dovrebbe essere possibile, vorrei solo modificare il 
puntatore alla tabella virtuale di B. E questo non avrebbe 
controindicazioni, visto che sia A che B hanno gli stessi dati membro e 
quindi la stessa dimensione. Il significato di questa operazione 
dovrebbe essere che l'oggetto è sempre lo stesso, con gli stessi 
attributi, ma cambia il suo stato.
Il metodo che utilizzo attualmente è il seguente:
class X {
   public:
   enum {A,B,C} type;
   void update() {
     switch(type) {
       case A:
        ...
        break;
       case B:
        ...
        type=A;
        break;
     }
   }
}
Però sarebbe utile poter utilizzare le funzioni virtuali e la gerarchia 
di classi, soprattutto in casi in cui alcuni dei "type" condividono 
alcune funzionalità.
E.

 };
Perché due classi identiche?


L'implementazione di update() sarà diversa. Diciamo:
void A::update() {x++;}
void B::update() {x--;}
E.

L'implementazione di update() sarŕ diversa.
Sě, scusa, a volte perdo colpi; č evidente che l'implementazione, poi,
si differenzia, perché ereditano entrambe da X.  Non capisco, perň,
perché non ti vada bene cosě.
Quello che scrivi:
class X {
   public:
   enum {A,B,C} type;
   void update() {
     switch(type) {
       case A:
        ...
        break;
       case B:
        ...
        type=A;
        break;
     }
   }
}
č esattamente quello che ottieni, nell'altra strada, con il
polimorfismo...

 polimorfismo...
Sì, ma è un po' più difficile da gestire. Mi sarebbe utile poter dire 
che alcuni tipi condividono le stesse funzioni, oppure la funzione 
update di un certo tipo chiama la funzione update del padre e poi fa 
altre operazioni... Con gli switch questo spesso risulta in duplicazione 
del codice.
E.

altre operazioni...
Questo è fattibile:
class Base {
public: 
  virtual void update() {
      std::cout << "Base::update()" << std::endl;
  }
};
class Derived: public Base {
public:
  void update() {
      Base::update();  
      std::cout << "Derived::update()" << std::endl;
  }
};

Una possibile soluzione potrebbe essere:
// l'oggetto di base
class X {
   protected:
   int n;
   friend class Type;
   Type *type;
   void update();
};
// l'ereditarietà la faccio su Type, invece che su X.
// ogni funzione di X ha una implementazione
// tramite funzione virtuale di Type.
class Type {
   public:
   virtual void update(X *obj)=0;
};
// l'update di X chiama una funzione diversa a seconda
// del Type
void X::update() {
   type->udpate(this);
};
// il tipo A
class TypeA: public Type {
   public:
   virtual void update(X *obj) {obj->n++;};
};
// il tipo B
class TypeB: public Type {
   public:
   virtual void update(X *obj) {
     obj->n--;
     // cambia tipo!
     Type *old=obj->type;
     obj->type=new TypeA;
     delete old; // me stesso!
   };
};
...non mi piace molto il fatto che per cambiare tipo risulta necessario
allocare una nuova classe (che contiene un solo puntatore!). A questo 
punto forse mi conviene riallocare l'intera classe X e copiare i dati.
La domanda diventa: è lecito in una funzione membro distruggere (delete) 
l'oggetto this? Dovrebbe essere sensato, se dopo averlo distrutto non si 
chiamano funzioni virtuali dello stesso oggetto. Ed è possibile creare 
un nuovo oggetto (della stessa dimensione) nella stessa zona di memoria 
puntata precedentemente da this? Come si fa?
E.

 In che senso?
Non avevo capito la tua obiezione, mi pareva che tu mi dicessi che 
andava bene il metodo che avevo scelto, con gli switch. E io ti stavo 
dicendo perché avrei preferirei il polimorfismo!
Il polimorfismo perň non mi va bene perché voglio che un oggetto possa 
cambiare tipo in run-time. Vedi l'altro messaggio che ho scritto.
E.

?manu* ha scritto:

Usando il C+ standard e le sue principali estensioni non e' possibile 
cambiare il tipo di una classe ne' modificare il puntatore alla tabella 
virtuale.
Il tuo problema e' descritto dal design pattern "Strategy", noto anche 
come "Policy".
Vedi: 
http://it.wikipedia.org/wiki/Strategy_pattern
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

cambiare tipo in run-time.
E' questo che non capisco: perché tu abbia bisogno che l'oggetto cambi
tipo a run-time. Non comprendo l'esigenza di base...

 puntata precedentemente da this? Come si fa?
Perche' farsi del male?


 Non c'e' bisogno di allocare niente.
Giusto!
grazie,
	E.

 sia un puntatore const X * const, e non plain X *).
No, questo non lo puoi fare. Ovvero lo puoi fare nella tua funzione 
statica, ma non nell'operator=. Se vuoi modificargli il puntatore sotto 
il naso, il chiamante deve essere informato!
Mi pare che quello che ti serve è implementare una classe puntatore ai 
tuoi oggetti cioè una classe che ridefinisce gli operatori ->, *, new e 
delete per i tuoi oggetti.
Comunque la mia richiesta era un po' diversa, e cioè quella di creare un 
nuovo oggetto ad una locazione prefissata. Mi ricordo che new utilizza 
gli allocatori, che dovrebbero permettere di gestire a proprio 
piacimento l'allocazione. Dunque la cosa dovrebbe essere fattibile (ma 
io non ho mai usato gli allocatori). Solo mi chiedevo se la strada è 
fattibile...
E.
E.

 nulla di validità generale, solo una particolare implementazione.
No, infatti. Lo heap non viene chiamato in causa.
E.

   private: static TypeA typeA;
Mi correggo:
    obj->type = &typeA;


 alcune funzionalità.
Allora riorganizza le tue classi in modo che le funzionalita'
condivise siano accessibili, ed usale opportunamente in base ai
cambiamenti di stato che osservi/generi.
Il nodo della questione e' che quello che ci hai presentato non e' il
tuo problema, ma un tentativo di soluzione che non funziona. In questo
modo risulta molto difficile darti consigli utili, salvo forse quello
ovvio: butta via tutto e ricomincia da capo, pensando meglio a come
funzionano i costrutti del linguaggio, invece di cercare di torcerli
per far loro fare quello che vuoi tu.
Ciao,
Nicola

 cambiare tipo in run-time. Vedi l'altro messaggio che ho scritto.
Un oggetto puo' cambiare tipo a runtime solo verso un altro tipo
presente nella sua gerarchia di super-classi, il che non mi pare
risolva il tuo problema.
Quello che puoi fare pero' e' spostare lo stato in una classe esterna
e sostituire l'oggetto del tipo di partenza con un nuovo oggetto del
tipo di arrivo, scambiando lo stato tra i due.
Ciao,
Nicola

 tipo di arrivo, scambiando lo stato tra i due.
Sì, infatti si è già discusso di questo nell'altra sotto-thread.
E.

 per far loro fare quello che vuoi tu.
In realtà io ho già il codice funzionante, strutturato come ho mostrato 
nel messaggio precedente (tramite switch...case). Solo che la struttura 
del problema richiama moltissimo una struttura gerarchica, e sto 
cercando di capire se c'è un modo di trarre vantaggio da questo.
Allora provo a presentare il problema in maniera brutale.
Mettiamo che debba scrivere un gioco. Il gioco presenta sullo schermo 
molti oggetti: personaggi, piattaforme, proiettili, stelline, monete... 
e quant'altro. Ognuno di questi oggetti ha delle sue proprietà. Tutti 
gli oggetti, ad esempio, hanno una posizione. Gli oggetti in movimento 
possono avere memorizzata la velocità corrente. Gli oggetti con una 
animazione hanno memorizzato il frame di animazione. Inoltre ogni 
oggetto del gioco può avere molti stati diversi. Una stellina può essere 
nello stato FADE_IN, SHINE, BLINK, FADE_OUT (per dire...).
Questa struttura porta subito alla mente una struttura gerarchica (non 
credo ci sia bisogno di spiegare perché). Inoltre avrei notevoli 
vantaggi nel poter usare le funzioni virtuali. Potrei dire ad un oggetto 
obj->hit() quando viene colpito, e l'oggetto potrebbe reagire in base 
alla sua classificazione gerarchica. Definendo nuovi oggetti posso 
ereditare tutte le funzioni dell'oggetto padre e modificare solo quello 
che c'é da modificare. Attualmente invece bisogna, spesso, duplicare il 
codice, perché l'attributo che identifica il tipo non è gerarchico ma 
lineare. In effetti, per come è organizzato il codice attualmente, c'è 
una contrapposizione tra il mantenere una unica variabile di stato e 
usare più variabili di stato. Con una sola variabile di stato c'é il 
problema che stati simili vanno comunque trattati come se fossero del 
tutto diversi. Con più variabili di stato c'é il problema che ogni stato 
avrebbe diversi sotto-stati, quindi una proliferazione di variabili.
L'OO invece sembrerebbe molto adeguato. L'unico problema è che vorrei 
poter trasformare un oggetto da un tipo ad un altro.
Quello che succede spesso è che un oggetto di un certo tipo si trasforma 
in un oggetto di un'altro tipo. Ad esempio quando il personaggio 
colpisce una stellina questa diventa una moneta.
L'approccio attuale che utilizzo (non OO) è che c'é un unica struttura 
che può rappresentare un qualunque oggetto. Il tipo di oggetto è una 
variabile int (una enum, per essere precisi). Quando la stellina diventa 
una moneta è sufficiente cambiare il valore di questo attributo... et 
voilà. Le coordinate x,y non vanno riscritte perché la stellina e la 
moneta hanno la stessa posizione.
L'approccio OO richiede invece di creare una nuova moneta, 
inizializzarla tramite la stellina e quindi cancellare la stellina. C'è 
una inutile richiesta e frammentazione di memoria e di overhead per la 
copia. Teniamo conto anche di avere risorse limitate (per esempio siamo 
su una console portatile). E teniamo conto che questi cambiamenti di 
stato avvengono continuamente.
Teniamo soprattutto conto che è possibile, con un "trucchetto", evitare 
questi problemi, cambiando brutalmente il puntatore alla tabella 
virtuale dell'oggetto. Per scrupolo oggi ho fatto una prova diretta, che 
vi riporto di seguito:
#include <cstdlib>
#include <iostream>
using namespace std;
class Base {
   public:
   virtual void who() {cout<<"BASE"<<endl;};
};
class Stella: public Base {
   public:
   virtual void who() {cout<<"STELLA"<<endl;};
};
class Moneta: public Base {
   public:
   virtual void who() {cout<<"MONETA"<<endl;};
};
struct Raw {
   void *vptr;
// assumo che il puntatore virtuale sia il primo attributo di ogni
// oggetto
};
void* moneta_vptr;
int main(int argc, char *argv[]) {
   Base *p=new Stella;
   {
     Moneta dummy;
     moneta_vptr=((Raw *)&dummy)->vptr;
   }
   p->who(); // ho una stella...
   ((Raw *)p)->vptr=moneta_vptr; // cambio il tipo dell'oggetto
   p->who(); // ho una moneta!
}
C'è un modo "pulito" per ottenere lo stesso risultato?
E.

virtuale dell'oggetto.
I "trucchetti" sono sempre pericolosi, anche perché non c'è modo
pulito per implementarli, essendo strettamente dipendenti dalla
piattaforma.

 piattaforma.
Certo, infatti quello che sto chiedendo č proprio se č possibile 
ottenere lo stesso risultato senza trucchetto. Il trucchetto mostra che 
la cosa "sarebbe" gestibile dal compilatore ma non sembra che si possa 
esprimere con il linguaggio.
Comunque sono convinto che il trucchetto funzionerebbe su ogni 
compilatore... l'unica assunzione che faccio č che le funzioni virtuali 
vengano implementate con una tabella virtuale, e che il puntatore a 
questa tabella sia il primo attributo dell'oggetto. Non vedo quale altra 
implementazione ci possa essere.
E.

implementazione ci possa essere.
Se vi fosse uno standard d'implementazione, sarebbe appunto previsto
dallo standard, ma così non è, quindi non si può fare alcuna
assunzione. D'altronde, non vedo cosa vieti a un compilatore di usare
un layout diverso per le strutture "di servizio".

 adeguata a rappresentare il tuo problema.
Forse si: 
http://www.eptacom.net/pubblicazioni/pub_it/nl_6.html
Non ho ben capito se e' proprio questo che serve a ?manu*; in tal caso 
l'articolo si spinge addirittura oltre, togliendo il requisito che non 
ci siano dati membro, quindi lui dovrebbe poter semplificare parecchio.
Certo ci sono svantaggi e limitazioni, per cui non e' detto che sia la 
soluzione migliore, ma anche questo mi sembra ben trattato.
  bye
	av.

Forse si: 
http://www.eptacom.net/pubblicazioni/pub_it/nl_6.html
Sě, ok, perň "la tecnica non č universalmente utilizzabile".

 "trucchetto", appunto.
E.

 soluzione migliore, ma anche questo mi sembra ben trattato.
Per chi ha ancora voglia di seguire la discussione c'è un punto che non 
capisco del tutto. Nell'esempio quando passa da un tipo ad un altro 
salva sullo stack tutti i dati membro prima della metamorfosi e ce li 
ricopia dentro dopo. Nel caso in cui due oggetti condividano alcuni dati 
membro (in quanto definiti in un antenato comune), non dovrebbe essere 
necessario farne una copia e poi re-inserirli, giusto? (ovviamente nel 
caso in cui né il distruttore né costruttore vadano a modificare i dati 
membro)
E.

 trovare il miglior trade/off nella soluzione ad ogni problema."
Che bello, peccato che "la tecnica" rimane "non universalmente
utilizzabile".
;-)


wrote:

L'approccio di Pescio e' interessante, ma il suo articolo non mi piace
perche' confonde la soluzione concettuale del problema con la sua
ottimizzazione a basso livello.
Sul piano logico le soluzioni possibili restano sempre due: o la
natura dell'oggetto e' rappresentata a livello di stato ed il
comportamento varia dinamicamente in funzione di tale stato, o
l'oggetto del tipo originario viene rimpiazzato con un oggetto del
nuovo tipo che ha una classe base in comune col precedente e che
presumibilmente deriva il suo stato iniziale da quello finale
dell'oggetto vecchio.
Se questa sostituzione risulta troppo onerosa si puo' ragionare sulle
possibili ottimizzazioni, la prima delle quali e' per l'appunto la
gestione ad hoc della memoria.
Ciao,
Nicola

 membro)
Mi sembra un approccio molto fragile. Basta che alla tua classe venga
aggiunto un membro con distruttore non triviale perche' il tutto
smetta di funzionare. Il C++ mette a disposizione dei meccanismi tutto
sommato poco costosi per ottenere maggior solidita'. Se non ti
interessa utilizzarli forse dovresti considerare l'ipotesi di
riscrivere la tua applicazione in C.
Ciao,
Nicola

 riscrivere la tua applicazione in C.
Sì. In effetti una delle "applicazioni" alla quale volevo applicare 
questo metodo è già scritta in C e perfettamente funzionante. Solo che 
il codice è ora difficile da estendere... e stavo meditando di passare 
al C++. In effetti potrei mantenerlo in C e implementare le tabelle 
virtuali a mano...
In un nuovo prototipo sto utilizzando il C++ e sto provando ad 
utilizzare il pattern "Strategy" suggerito da Carlo Milanesi.
E.