Newsgroup: it.comp.lang.c++
Titolo: ereditarietà virtuale?

Ho una classe A a base di una gerarchia di oggetti che vengono messi in 
un albero. E ho una classe B che aggiunge una funzionalità ad A:
class B: public A {
public:
	virtual void bFunction()=0;
};
In questo modo posso utilizzare il dynamic_cast sui puntatori ad A 
presenti nell'albero per verificare se posso utilizzare questa 
funzionalità aggiuntiva.
Ora io ho anche una classe
class C: public A {
   /*...*/
};
che fa le sue cosette e vorrei ora dichiarare una classe D che eredita 
da C e che implementa la funzione bFunction() di B:
class D: public C, public B {...};
Ora però D contiene due copie di A! Mi verrebbe quindi da modificare la 
dichiarazione di B utilizzando l'ereditarietà virtuale:
class B: public virtual A {...};
E' la cosa giusta da fare? ...ho sempre avuto un po' di paura 
dell'ereditarietà virtuale... In particolare è giusto mettere 
l'ereditarietà virtuale in B ma non in C?
E.

 E' la cosa giusta da fare? 
Non avevo ancora provato a farla veramente... ho ottenuto un "internal 
compiler error"!!! Evidentemente anche il mio compilatore non č molto a 
suo agio con l'ereditarietą virtuale. Per chi fosse curioso ecco il 
codice incriminato:
class A {
	virtual void update();
};
class B: public virtual A {
	virtual void hit()=0;
};
class C: public A {
};
class D: public B {
	void hit() {};
};
static void f() {
	A *a;
	static_cast<B *>(a)->hit();
};
Ecco l'errore riportato dal compilatore:
Error   : illegal type cast
     static_cast<B *>(a)->hit();
Error   : internal compiler error
while executing in file 'CClass.c' line: 1203
(compiling 'f')
     static_cast<B *>(a)->hit();

 l'ereditarietà virtuale in B ma non in C?
Se A e' una classe virtuale pura non ci sono problemi, altrimenti
bisogna starci attenti.
Definire A come ereditata virtualmente da una parte e dall'altra no
non ha molto senso. Se non sbaglio pero' in un caso simile vince
l'eredita' virtuale.
Ciao,
Nicola

 l'eredita' virtuale.
Però a me sembra avere senso. Infatti la gerarchia contenente la classe 
A potrebbe essere disponibile in una libreria (e quindi non 
modificabile). E potrebbe aver senso definire una classe B come nel mio 
caso.
In effetti quello che sto cercando di esprimere con B è una interfaccia 
ad oggetti che implementano una certa funzione e che derivano da A.
E.

 
ricapitolando:
class A
{
public:
   virtual void f1() {}
   virtual void f2() {}
};
class B : public A
{
public:
   virtual void bFunction()=0;
};
class C : public A
{
  //...
};
se le cose stanno veramente cosě io farei (seguendo la regola che 
containment + delega in generale č meglio per ereditare 
l'implementazione, tanto piů se sono solo due funzioni!):
class D : public B  //qui eredito solo l'interfaccia
{
public:
   virtual void f1() { m_Impl.f1() }
   virtual void f2() { m_Impl.f2() }
   virtual void bFunction()
   {
     //...
   }
private:
   C m_Impl; //e qui l'implementazione
};
-
GM

 funzionalità aggiuntiva.
Perché non rendere B una classe non derivata da A? e derivare una classe
AWithB da A e da B, e idem per D che diventa CWithB...
Insomma
class A[};
class B{};
class AWithB : public A, public B {};
class C : public A {};
class CWithB : public C, public B {};
Oppure utilizzare la composizione e un pattern di tipo delegate?

 Perché non rendere B una classe non derivata da A? 
Sì, funziona! Ero convinto che non fosse possibile fare un dynamic_cast 
tra classi scorrelate. Invece se ho:
class A {virtual void aFunction();};
class B {virtual void bFunction();};
class C: public A {};
Posso fare:
	C c;
	B *bPtr=&c;
	A *aPtr=dynamic_cast<A*>(bPtr);
E ottengo in effetti un puntatore valido. Pensavo che non fosse 
possibile, in quanto sto convertendo un puntatore a B ad un puntatore ad 
A e le classi A e B sono del tutto scorrelate!
E.

?manu* ha scritto:

Non è un uso corretto.
Il primo cast è stile C e se provi a richiamare qualche metodo da bPtr 
vai in contro a guai. Nel secondo cast funziona perché probabilmente non 
interessa sapere il tipo effettivo di bPtr, ma solo se da esso è 
possibile risalire ad A.
Perché non provi un approccio di questo tipo?
//-----------------------------------------------
class A;
//-----------------------------------------------
class F
{
   public:
     F() {}
     virtual ~F() {}
     virtual void funct(A& obj) = 0;
};
//-----------------------------------------------
class A
{
   public:
     A() { }
     virtual ~A() {}
     virtual void funct(F& f) { f.funct(*this); }
};
//-----------------------------------------------
class Bf : public F
{
   public:
     Bf() {}
     virtual ~Bf() {}
     virtual void funct(A& obj) { ... };
};
//-----------------------------------------------
class Cf : public F
{
   public:
     Cf() {}
     virtual ~Cf() {}
     virtual void funct(A& obj) { ... };
};
//-----------------------------------------------
   Bf bf;
   A a;
   a.funct(bf);
   Cf cf;
   a.funct(cf);
//-----------------------------------------------
Ciao

 caso.
Guarda, se hai ereditarietà virtuale, usala in finchè necessario. Male
che vada non entra in gioco. Altrimenti ristruttura il codice in modo da
non avere il diamante e basta.
 
-- 
cc = callcc {|cc| cc.call cc}; cc.call cc


Ah, scusa, ho sbagliato a scrivere! La classe C eredita sia da A che da B:
class A {virtual void aFunction();};
class B {virtual void bFunction();};
class C: public A, public B {};
C c;
B *bPtr=&c;
A *aPtr=dynamic_cast<A*>(bPtr);
In tal caso il codice è corretto? La cosa un po' strana è che io 
converto un puntatore a B in un puntatore ad A quando A e B sono legati 
solo dal fatto di avere una classe derivata C in comune.
E.

 }
Sě, č piů elegante. Ma un po' mi secca utilizzare una variabile locale. 
Quando devi fare molti tentativi con il dynamic_cast, ti riempi di 
variabili locali che differiscono solamente per il tipo:
void f(A* ptr) {
   B* bptr=dynamic_cast<B*>(ptr);
   if (bptr) {
	...
   } else {
      C *cptr=dynamic_cast<C*>(ptr);
      if (cptr) {
         ...
      }
   }
}
Immagino che il compilatore saprebbe ottimizzare per bene la cosa, ma 
comunque mi da fastidio avere molti nomi di variabili in giro...
E.

 E.
l'indirizzo di un oggetto resta sempre lo stesso, sia che lo converti in 
un puntatore ad una classe leggittima sia che lo converti in un 
puntatore a void, quindi penso che il compilatore sappia ricavare la 
classe desiderata partendo da quello, però stilisticamente è sbagliato.
Forse quello è un semplice esempio, ma non potresti fare semplicemente:
C c;
A *aPtr=dynamic_cast<A*>(&c);
e comunque rivedrei la struttura generale, quando si cominciano ad 
utilizzare troppo i dynamic_cast si sente puzza di pezza aggiuntiva.
Anziché l'ereditarietà multipla ti consiglierei di usare l'aggregazione 
con una class factory per la creazione dell'oggetto da aggregare.
L'ereditarietà multipla può andare bene per oggetti usa e getta e, 
possibilmente, non virtuali, altrimenti, a lungo andare, è solo una rogna.
Ciao

 possibilmente, non virtuali, altrimenti, a lungo andare, è solo una rogna.
In questo caso sto utilizzando l'ereditarietà multipla per implementare 
quello che in Java sarebbero le interfaccie. Non so cosa sia 
l'"aggregazione", e non capisco bene cosa c'entri la "class factory" 
(ammesso che io interpreti correttamente il significato).
Provo a spiegare il problema per intero, nel caso qualcuno abbia voglia 
di darmi ulteriori suggerimenti.
Ho una struttura ad albero contenente oggetti che implementano i metodi 
draw() e update(). In effetti questi sono oggetti che vanno disegnati a 
schermo, e che sono gerarchicamente organizzati. Ora succede che certi 
nodi conterranno come figli degli oggetti specializzati, ovvero oggetti 
che oltre "draw/update" implementano anche un altro metodo, diciamo 
"hit". Altri oggetti della gerarchia potrebbero anche utilizzare altre 
funzionalità in comune, ad esempio potrebbero avere una dinamica da 
oggetto fisico (posizione/velocità).
Dunque la mia struttura è:
class Object {
public:
	virtual void draw() const;
	virtual void update();
};
class Hittable {
public:
	virtual bool hit()=0;
};
class InertialObject: public A {
private:
	Vector pos;
	Vector vel;
public:
	void updatePos();
};
Ora il problema viene quando ho un Hittable IntertialObject, che io 
attualmente faccio così:
class D: public Hittable, public InertialObject {};
La cosa è abbastanza naturale (dal mio punto di vista!). Hittable è una 
interfaccia, mentre InertialObject contiene dei dati suoi. Dunque è 
sensato che Hittable non erediti da Object, mentre InertialObject sì.
Quello che poi accade è che ho un nodo dell'albero che contiene solo 
oggetti di tipo D. Solo che gli oggetti sono memorizzati con puntatori 
ad Object. Dunque il nodo contenente gli oggetti D, per accedere al 
metodo hit dei propri figli, deve convertire il puntatore ad Object in 
un puntatore a Hittable. Quindi ci accedo così:
void DContainer::update() {
	for (A* ptr=begin();ptr!=end();ptr=ptr->next) {
		Hittable *hptr=dynamic_cast<Hittable *>(ptr);
		if (hptr && hptr->hit()) {
			// bla bla...
		}
	}
}
La cosa curiosa, che ancora non ho capito del tutto, è che pur sapendo 
che gli oggetti SONO di tipo D, non posso utilizzare
	static_cast<B*>(ptr);
che dà errore in compilazione. Evidentemente il passaggio tra le due 
classi parallele può avvenire solo in maniera dinamica!
Tutto sto casino deriva dal fatto che, per semplicità, voglio mantenere 
una struttura omogenea in tutto l'albero. L'alternativa sarebbe quella 
di usare pesantemente i template in modo che ogni nodo sia una classe a 
sè stante che sappia esattamente il tipo dei propri figli.
La soluzione con i template è stata in effetti il mio primo tentativo. 
Funzionava anche, ma era per me troppo difficile da gestire. In 
particolare ogni classe dipendeva dal tipo proprio, da quello dei figli 
e da quello del parent... creando delle dipendenze circolari che poi non 
riuscivo a districare.
E.

 E.
Intanto, se non sono molti i metodi puri della classe Hittable e se ha 
senso, sposterei i metodi direttamente in Object. Eventualmente puoi far 
  tornare semplicemente false da hit() anziché renderlo puro.
Così, in questo caso, non hai più bisogno del dynamic_cast.
Oppure, come già detto, mantieni un oggetto Hittable, come puntatore o 
reference, dentro Object.
Poi sposterei la logica dal container all'oggetto. Non è lui che deve 
prendere decisioni, ma l'oggetto stesso.
Per inciso, il pezzettino di esempio che avevo postato l'ho estrapolato 
da un mini screensaver 2D semplice semplice, con oggetti che si muovono 
sullo schermo (che visualizzano valori vari), senza gestione delle 
collisioni.
Ciao

 collisioni.
Quel codice non l'avevo capito. In base a quello che mi dici ora credo 
di averlo capito. Il punto è sempre che sposti il metodo "facoltativo" 
nella classe base.
E.

 comunque mi da fastidio avere molti nomi di variabili in giro...
Allora, in primo luogo non sono completamente sicuro che il compilatore
ottimizzi la cosa. Potrebbe farlo come no. Tuttavia sacrificare a fronte
di una micro-ottimizzazione codice lineare IMHO è piuttosto sbagliato.
Se ci si rende conto che quel codice è un collo di bottiglia, si farà
tutto il necessario per evitarlo. 
Inoltre se avessi una gerarchia di classi che deve decidere
*pesantemetne* a runtime quale è il tipo dinamico, mi chiederei se non
sto sbagliando qualcosa. Oltre a non essere codice eccessivamente
efficiente io lo trovo anche bruttarello.
Spesso, ma non sempre, usando opportunamente i design pattern si riesce
a riprogettare il tutto in modo che vengano fatte le cose giuste 'e
basta'. Oppure si può considerare di usare il polimorfismo statico
invece di quello dinamico. 
Ecco, io mi sono reso conto che da quando ho sventrato per bene i
templates il mio codice C++ usa più spesso i templates e assomiglia meno
a 'codice Java corrispondente'. 


  sbagliato.
Sono abbastanza sicuro che i puntatori cambino, proprio per ovviare a
problemi di ereditarietà multipla non virtuale.
A logica non vedo il modo in cui si potrebbe risolvere il classico
diamond in cui B e C derivano da A, e D deriva da B e C, D ha DUE
rappresentazioni di A che possono essere puntate indipendentemente
risalendo la gerarchia dall'una o dall'altra parte. Se il puntatore
non cambiasse, non potrei distinguere le due diverse istanze di A.
--*PaN!*
 
-- 
I'm using an evaluation license of nemo since 201 days.
You should really try it!
http://www.malcom-mac.com/nemo

 in stile C.
Ma anche no, io farei un upcast (statico o automatico) alla
superclasse comune e un dynamic cast alla sottoclasse. Capisco la tua
confusione, dovuta all'ipotesi errata che "i puntatori sono tutti
uguali", ma non essendo (in generale) cosi' il reinterpret_cast non e'
valido.
--*PaN!*
 
-- 
I'm using an evaluation license of nemo since 202 days.


 non cambiasse, non potrei distinguere le due diverse istanze di A.
Sì pare anche a me. Ma non serve un esempio così complicato, nel momento 
in cui hai:
class A: public B, public C {};
Quando fai:
A a;
B* bptr;
C* cptr;
bptr=&a;
cptr=&a;
non è possibile che bptr e cptr siano uguali, perchè i dati membro di a 
e di c si trovano necessariamente in posizioni diverse in memoria. Con 
l'ereditarietà singola questo problema non c'è perchè si può usare la 
convenzione di mettere i dati della classe base sempre prima di quelli 
delle classi derivate.
E.

 di quelli delle classi derivate.
Volevo un esempio con puntatori allo stesso tipo che fossero diversi,
pur partendo da un solo oggetto. Per questo ho preferito l'esempio
diamond :)
 
-- 
I'm using an evaluation license of nemo since 202 days.
