Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
C/C++ - Problema con array di classi virtuali
Forum - C/C++ - Problema con array di classi virtuali

Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 12:25
Domenica, 10/07/2011
Salve a tutti.
Stò lavorando ad un bel progettone ed ho bisogno di un'estrema malleabilità delle classi, perciò ho ben pensato di lavorare sulle classi virtuali come base di altre classi attaccate ad esse. E' andato tutto bene finché mi sono ritrovato davanti al problema nel passare un array di classi derivate in una funzione che avrebbe gestito la classe virtuale, padre delle classi figlie che gli avevo appena passato. Risultato? I membri sono completamente deallineati. Posto un esempio estremamente semplificato di quello che stò cercando di fare:
Codice sorgente - presumibilmente C#

  1. #include <stdio.h>
  2.  
  3. class Base
  4. {
  5. public:
  6.     int x;
  7.     int y;
  8.     int z;
  9.     virtual void Create(int x, int y, int z) = 0;
  10. };
  11. class Blah : public Base
  12. {
  13. public:
  14.     int w;
  15.     int h;
  16.     virtual void Create(int _x, int _y, int _z)
  17.     {
  18.         x = _x;
  19.         y = _y;
  20.         z = _z;
  21.         w = x + y;
  22.         h = y + z;
  23.     }
  24. };
  25.  
  26. void PrintParam(Base *base)
  27. {
  28.     printf("%i %i %i\n", base->x, base->y, base->z);
  29. }
  30. void PrintParams(Base *base, size_t arraySize)
  31. {
  32.     for(int i=0; i<arraySize; i++)
  33.         PrintParam(&base[i]);
  34. }
  35.  
  36. int main()
  37. {
  38.     const size_t arraySize = 10;
  39.     Blah *asd = new Blah[arraySize];
  40.     for(int i=0; i<arraySize; i++)
  41.         asd[i].Create(i, i+1, i*2);
  42.     for(int i=0; i<arraySize; i++)  //  WORK
  43.         PrintParam(&asd[i]);
  44.     PrintParams(asd, arraySize);    // DOESN'T WORK
  45.     delete asd;
  46. }



I membri della classe Base saranno spostati in base alla grandezza totale dei membri in più nella classe Blah. E' quasi ovvio, perché quando si passa al primo elemento dell'array, lui prende la dimensione della classe e la moltiplica per l'indice, ottenendone la posizione. Quindi se la classe Base è di 12 byte e Blah è di 20, in un array di tipo Blah usato come tipo Base, il primo elemento sarà all'offset 0, il secondo però sarà all'offset 12 invece che 20, il terzo sarà al 24 invece che al 40 e così via, disperdendo il valore reale delle variabili. Spero di essere stato il più chiaro possibile :-|. Aspetto vostre idee e pareri :idea:

Ultima modifica effettuata da xeeynamo il 10/07/2011 alle 12:28
PM Quote
Avatar
nessuno (Normal User)
Guru^2


Messaggi: 5475
Iscritto: 03/01/2010

Segnala al moderatore
Postato alle 13:31
Domenica, 10/07/2011
Beh, mi sembra che non ci sia un "errore" vero e proprio ma che sia normale ...

A parte il problema "didattico", non capisco dove tu voglia arrivare dato che non ha senso scrivere quel codice ...

Se proprio lo volessi fare funzionare, basterebbe correggere il (normale) comportamento del compilatore nel calcolare gli offset dei membri tra i puntatori di diverso tipo.

Codice sorgente - presumibilmente C/C++

  1. void PrintParams(Base *base, size_t arraySize)
  2. {
  3.     int offs = sizeof(Blah)-sizeof(Base);
  4.  
  5.     for(unsigned int i=0; i<arraySize; i++)
  6.         PrintParam((Base *)(((char *)&base[i])+i*offs));
  7. }



Un errore invece c'è (e potrebbe essere più grave) ... Alla fine la delete dovrebbe essere

Codice sorgente - presumibilmente Plain Text

  1. delete [] asd;



dato che si riferisce ad un array di oggetti e che i distruttori di *tutti* gli elementi devono essere chiamati.


Ultima modifica effettuata da nessuno il 10/07/2011 alle 13:32


Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
PM Quote
Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 14:14
Domenica, 10/07/2011
Testo quotato

Postato originariamente da nessuno:

Beh, mi sembra che non ci sia un "errore" vero e proprio ma che sia normale ...

A parte il problema "didattico", non capisco dove tu voglia arrivare dato che non ha senso scrivere quel codice ...

Se proprio lo volessi fare funzionare, basterebbe correggere il (normale) comportamento del compilatore nel calcolare gli offset dei membri tra i puntatori di diverso tipo.

Codice sorgente - presumibilmente C/C++

  1. void PrintParams(Base *base, size_t arraySize)
  2. {
  3.     int offs = sizeof(Blah)-sizeof(Base);
  4.  
  5.     for(unsigned int i=0; i<arraySize; i++)
  6.         PrintParam((Base *)(((char *)&base[i])+i*offs));
  7. }



Ciò che sto creando sarà strutturato in maniera estremamente modulare, per esempio ho la classe Picture, classe virtuale padre di classi figlie come BMP, GIF, DDS e così via. Col tuo sistema, ogni volta che aggiungo un "formato", dovrei prima di tutto aggiornare la classe Picture, idem se volessi rimuoverlo, quando preferirei qualcosa di completamente automatico. Però ti ringrazio perché ho trovato un'idea forse risolutiva: se ad esempio carico una BMP, farò sizeof(BMP)-sizeof(Picture) e la memorizzerò in Picture, poi farò una funzione chiamata GetPicture(int i) che partirà dal primo elemento dell'array e ricaverà l'indirizzo dell'elemento selezionato dall'indice i. Lo so che il compilatore non fa altro che comportarsi normalmente, di fatti non ho parlato di errori, ma mi stavo mangiando la testa perché magari mi era sfuggita una soluzione più semplice.




Testo quotato

Postato originariamente da nessuno:
Un errore invece c'è (e potrebbe essere più grave) ... Alla fine la delete dovrebbe essere
Codice sorgente - presumibilmente Plain Text

  1. delete [] asd;


dato che si riferisce ad un array di oggetti e che i distruttori di *tutti* gli elementi devono essere chiamati.


Ops xD

PM Quote
Avatar
nessuno (Normal User)
Guru^2


Messaggi: 5475
Iscritto: 03/01/2010

Segnala al moderatore
Postato alle 14:19
Domenica, 10/07/2011
Sì, ma intendevo dire che non dovresti trovarti nelle condizioni di cui parli.

Se crei una classe Picture e poi da questa derivi i vari formati particolari, perché dovresti avere i problemi che ti sei "creato" nel codice d'esempio che hai mostrato?

Prova a scrivere le classi effettive che andrai ad utilizzare e mostrare dove trovi il problema ...


Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
PM Quote
Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 16:07
Domenica, 10/07/2011
http://www.2shared.com/file/ECuzzpc2/blahblah.html qui ho messo parte del sorgente. I commenti come "BLAH" sono parti del codice rimosse, inutili perché non c'entrano niente col problema ;). La classe Image.h è la classe virtuale dove si appoggiano i due formati IMGD e TM2 (texture della PS2). Le due classi IMGD e TM2 inoltre si appoggiano alla classe File, che si occuperà dell'apertura dei file nonché dei messaggi di errore.WinBridge.h sostituirà le classiche funzioni dello standard C come fopen, malloc ecc con le API di Windows. D3D9Texture sarà la classe che creerà le texture partendo dalla classe Image (quindi indipendentemente dal formato di immagine caricato, la texture verrà creata e visualizzata correttamente) che chiamerà la funzione interna della libreria D3D9 CreateTexture dalla funzione RenderModel per creare le texture del modello 3D ed applicarle. Ovviamente il codice funziona alla perfezione, ma solo se vi è una texture nel modello 3D. Il framework dovrà essere in grado di caricare diversi formati di modelli 3D con diversi tipi di texture, ecco perché ho bisogno di fare tutti questi giri tra classi virtuali etc :). E' la prima volta che programmo usando così tante classi (quelli del link sono 16 dei 50 file di tutto il progetto) e non so esattamente come si gestiscono più formati, quindi se magari ho sbagliato qualcosa oppure ho progettato male sin dall'inizio la struttura del progetto, gradirei consigli da una persona sicuramente più esperta =)

PM Quote
Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 21:43
Domenica, 10/07/2011
Risolto con un semplicissimo Get:
Codice sorgente - presumibilmente C#

  1. #include <stdio.h>
  2.  
  3. class Base
  4. {
  5. public:
  6.     int x;
  7.     int y;
  8.     int z;
  9.     virtual void Create(int x, int y, int z) = 0;
  10.     virtual Base *Get(int i)
  11.     {
  12.         return this+i;
  13.     }
  14. };
  15. class Blah : public Base
  16. {
  17. public:
  18.     int w;
  19.     int h;
  20.     virtual void Create(int _x, int _y, int _z)
  21.     {
  22.         x = _x;
  23.         y = _y;
  24.         z = _z;
  25.         w = x + y;
  26.         h = y + z;
  27.     }
  28.     virtual Base *Get(int i)
  29.     {
  30.         return this+i;
  31.     }
  32. };
  33.  
  34. void PrintParam(Base *base)
  35. {
  36.     printf("%i %i %i\n", base->x, base->y, base->z);
  37. }
  38. void PrintParams(Base *base, size_t arraySize)
  39. {
  40.     for(int i=0; i<arraySize; i++)
  41.         PrintParam(base->Get(i));
  42. }
  43.  
  44. int main()
  45. {
  46.     const size_t arraySize = 10;
  47.     Blah *asd = new Blah[arraySize];
  48.     for(int i=0; i<arraySize; i++)
  49.         asd[i].Create(i, i+1, i*2);
  50.     for(int i=0; i<arraySize; i++)
  51.         PrintParam(&asd[i]);
  52.     PrintParams(asd, arraySize);
  53.     delete []asd;
  54. }


I risultati stampati sono uguali. Praticamente, ogni classe sia virtuale sia figlia, dovrà avere un Get(int i) dove i stà per indice. Dovrà sempre essere usato il primo elemento dell'array (quindi asd[0].Get(i) oppure asd->Get(i)) e ritornerà la posizione corretta della classe, grazie ad un semplice "return this+i". Spero che la soluzione possa essere d'aiuto a qualcuno in futuro :)

PM Quote
Avatar
nessuno (Normal User)
Guru^2


Messaggi: 5475
Iscritto: 03/01/2010

Segnala al moderatore
Postato alle 21:48
Domenica, 10/07/2011
Beh, è ovvio che con un metodo specifico delle due classi risolvi !
Ogni metodo "sa" come è fatta la propria classe e quindi gestisce correttamente l'offset dei membri.

Quello che mi sembrava tu volessi, fosse "accedere in memoria" con dei puntatori con un sistema che valesse sia per la classe base che per quella derivata.

Non ci siamo capiti ...


Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
PM Quote
Avatar
xeeynamo (Normal User)
Pro


Messaggi: 66
Iscritto: 14/03/2008

Segnala al moderatore
Postato alle 22:00
Domenica, 10/07/2011
Testo quotato

Postato originariamente da nessuno:

Beh, è ovvio che con un metodo specifico delle due classi risolvi !
Ogni metodo "sa" come è fatta la propria classe e quindi gestisce correttamente l'offset dei membri.

Quello che mi sembrava tu volessi, fosse "accedere in memoria" con dei puntatori con un sistema che valesse sia per la classe base che per quella derivata.

Non ci siamo capiti ...


No problem ;D

PM Quote