Newsgroup: it.comp.lang.c++
Titolo: union o virtual?

Ciao a tutti,
avrei una domanda da PROFANO del C++:
supponete di avere un triangolo, un quadrato e un cerchio. Per definire
queste
figure avro' bisogno rispettivamente di: base e altezza, raggio, lato.
Ora devo accedere a queste informazioni.
Una possibilita' e' dichiarare una lista di strutture, ognuna delle quali
possiede un campo di tipo enum (che mi specifica la figura) e un altro
campo di tipo union che mi raccoglie le informazioni geometriche. Se poi
devo usare funzioni (ad es "draw"), dovro' scrivere una funzione che, con un
meccanismo
tipo "switch-case" va a beccarsi la parte di codice da applicare in base
alla figura.
L'altra possibilita' (ditemi se sbaglio) e' dichiarare il classico oggetto
"shape", con la funzione
virtuale "draw". Poi derivo le classi triangolo, quadrato e cerchio. Quando
sara' il momento
di usare il triangolo faro' &shape = &triangolo e usero' shape.draw().
Il punto che, anche qui, dovro' usare un meccanismo "switch-case" del tipo:
switch {
 case triangolo: &shape = &triangolo; break;
 case quadrato: &shape = &quadrato; break;
 case cerchio: &shape = &cerchio; break;
}
e poi usare shape.draw().
DOMANDA: e' possibile evitare di usare "switch-case" ?
Come accedo ai dati geometrici delle figure (forse con shape.altezza, nel
caso di triangoli)?
QUELLO CHE VORREI: definirmi una classe (magari derivata) per ogni tipologia
di oggetto,
e poi accedere ai dati/metodi senza ricorrere a trucchi tipo "C"
Scusate per la banalita' e grazie.
--
Tiberio Grasso
Tecnomare SpA
R&D, Robotics and Space
Tel: 
+39-041-796711
Fax: 
+39-041- 796800
e-mail: 
gras...@tecnomare.it

Scusate per la banalita' e grazie.
Per carita`; il fatto che siano temi _fondamentali_ non ne
fa certo dei temi _banali_, ANZI, sono MOLTO profondi!
Alex

La soluzione e` proprio quella che hai intuito, anche se mostri di avere un po' di
confusione.
La strada da seguire e` questa:
1) dichiarare una classe astratta shape che esporti l'interfaccia generica delle
figure geometriche; shape potrebbe anche contenere una parte di implementazione
comune a tutte le possibili sottoclassi. I metodi dell'interfaccia pubblica saranno ovviamente
virtuali (eventualmente funzioni virtuali pure).
2) deriva da shape le sottoclassi quadrato, triangolo... e implementa i metodi virtuali.
3) quando nel tuo programma hai uno shape* oppure uno shape& non devi preoccuparti
di nulla, chiama semplicemente i metodi che ti occorrono nel modo usuale.
Es.
class Shape {
  public:
    /* dichiarazioni varie */
    virtual void Draw() = 0;     // funzione virtuale pura
  private:
     /* eventuale implementazione comune alle sottoclassi */
};
class Triangle : public Shape {
  public:
   /* dichiarazioni varie */
   virtual void Draw();     // devi implementare i metodi virtuali puri
  private:
    /* implementazione di Triangle */
};
void Triangle::Draw() {
  /* codice specifico per disegnare un Triangle e solo un Triangle*/
}
/* altre classi derivate */
/* spezzone di main: */
Shape* Triangolo = new Triangle(/*parametri costruttore*/);
...
...
...
Triangolo->Draw();  // uso direttamente la var Triangolo, ma poteva essere un Shape*
                                       // di cui non sapevo a quale tipo effettivamente puntasse.
Saluti,
Paolo
P.s.
   Dovendo lavorare con oggetti polimorfi, sara` bene che ogni classe abbia un distruttore (anch'esso
   virtuale). Non e` sempre necessario, comunque e` buona norma e pur sempre una precauzione.
----------------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
  e-mail: 
mar...@cli.di.unipi.it
 
----------------------------------------------------------

    }
Ciao, e grazie per la risposta esauriente.
Una sola cosa: ho capito male o il dynamic_cast torna NULL quando pShape non
e' un con_angolo?
Grazie ancora.
Tiberio Grasso
gras...@tecnomare.it


-
Ciao e grazie per la risposta esauriente.
Un solo chiarimento: come mai e' "quasi" necessario avere un distruttore
per gli oggetti polimorfi?
Grazie e ciao.
Tiberio Grasso
e-mail: 
gras...@tecnomare.it

 per gli oggetti polimorfi?
Dalla FAQ-lite di comp.lang.c++ (per gentile concessione di Marshall Cline):
[20.4] When should my destructor be virtual?
 
When you may delete a derived object via a base pointer.
 
Virtual functions bind to the code associated with the class of the object,
rather than with the class of the pointer/reference.  When you say
delete basePtr, and the base class has a virtual destructor, the destructor
that gets invoked is the one associated with the type of the object *basePtr,
rather than the one associated with the type of the pointer.  This is generally
A Good Thing.
 
TECHNO-GEEK WARNING; PUT YOUR PROPELLER HAT ON.
Technically speaking, you need a base class's destructor to be virtual if and
only if you intend to allow someone to invoke an object's destructor via a base
class pointer (this is normally done implicitly via delete), and the object
being destructed is of a derived class that has a non-trivial destructor.  A
class has a non-trivial destructor if it either has an explicit destructor, or
if it has a member object or a base class that has a non-trivial destructor
(note that this is a recursive definition (e.g., a class has a non-trivial
destructor if it has a member object (which has a base class (which has a
member object (which has a base class (which has an explicit destructor)))))).
END TECHNO-GEEK WARNING; REMOVE YOUR PROPELLER HAT
 
If you had a hard grokking the previous rule, try this (over)simplified one on
for size: A class should have a virtual destructor unless that class has no
virtual functions.  Rationale: if you have any virtual functions at all, you're
probably going to be doing "stuff" to derived objects via a base pointer, and
some of the "stuff" you may do may include invoking a destructor (normally done
implicitly via delete).  Plus once you've put the first virtual function into a
class, you've already paid all the per-object space cost that you'll ever pay
(one pointer per object; note that this is theoretically compiler-specific; in
practice everyone does it pretty much the same way), so making the destructor
virtual won't generally cost you anything extra.
-- 
Maurizio Loreti                      
http://wwwcdf.pd.infn.it/~loreti/mlo.html
Un. of Padova, Dept. of Physics - Padova, Italy          
lor...@padova.infn.it

e' un con_angolo?
Si`, quando fai il dynamic_cast fra puntatori polimorfi, il comportamento
e` proprio questo; e funziona benissimo anche fra puntatori a tipi (purche`
polimorfi) *non parenti* fra loro (quindi, in casi di eredita` multipla), e anche
in casi complessi come basi virtuali, eccetera.
Il dynamic_cast fra REFERENCE (sempre polimorfe), invece, lancia una
_exception_ quando l'argomento non e`-un oggetto del tipo parametrico;
quindi, e` piu` appropriato del dynamic cast fra puntatori in quei casi in
cui e` "eccezionale" (raro, "sbagliato", ecc ecc) questo non-essere-un,
mentre il cast dinamico fra puntatori si puo` usare in cui il non-essere-un
e` non-eccezionale (questa e` la distinzione strutturale fra exception e non,
e viene reinforzata dalle normali implementazioni: una "possibile exception"
che in pratica non succede, risulta normalmente, con un buon ottimizzatore, 
piu` performante di un equivalente if [mai soddisfatto o sempre soddisfatto],
ma molto piu` onerosa da catturare e gestire in quei casi [dovrebbero essere 
molto rari: appunto, eccezionali] in cui si verifica).
Attento che con molti compilatori C++ attuali devi esplicitamente abilitare
la "run-time type identification" (dynamic_cast, ecc), e/o le eccezioni,
a tempo di compilazione (con uno switch, o simili), se no "per default"
l'una, l'altra, o entrambe queste feature, sono lasciate disabilitate.
Alex

per gli oggetti polimorfi?
Ho visto che ti hanno gia` risposto, per completezza mi limito a qualche esempio:
1) Nel tuo caso, supponi che Shape abbia un po' di implementazione e un distruttore virtuale:
    class Triangle {
       public:
        Triangle();
         ~Triangle();    // non virtuale!
         /* dichiarazioni */
      private:
         Point* VertexPtr; 
    };
   Triangle::Triangle() { VertexPtr = new Point[3]; }
   Triangle::~Triangle() { delete[] VertexPtr; }
   // frammento main 
  Triangle* a = new Triangle;
  Triangle* b = new Triangle;
   delete a;  // tutto ok;
   Shape* c = b;
   delete b;   // errore il compilatore non sa che *c e` un Triangle e esegue il distruttore di Shape
                       // anzicche` quello di Triangle.
Se il distruttore fosse stato virtual, il late binding avrebbe permesso l'invocazione di quello
corretto anche se il puntatore c e` di tipo Shape e non Triangle;
Ovviamente se gli oggetti venissero distrutti tramite il corretto tipo di puntatore non sarebbe necessario
avere distruttori virtuali (basta quello normale), ma in generale si tratta di una possibilita` remota.
Paolo
