Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
C/C++ - Polimorfismo?
Forum - C/C++ - Polimorfismo?

Pagine: [ 1 2 ] Precedente | Prossimo
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 13:18
Martedì, 11/12/2018
Buongiorno. Se vi fosse possibile, avrei bisogno una conferma o un "indirizzo" in caso non ci fosse gran che da confermare.

Per un programmino che ho in mente, dovrei implementare un metodo per caricare da un file di testo una "catena" di comandi che intendo identificare tramite etichette testuali (vorrei che il file fosse leggibile anche "a occhio nudo") e serie di dati correlati al comando sulla stessa riga. Ciascun comando dovrebbe dar luogo a comportamenti in qualche modo imparentati tra loro, per cui pensavo di creare una classe COMANDO con gli aspetti generici, dalla quale derivare i diversi tipi con le caratteristiche più specifiche. C++, dunque, ed ereditarietà (e già questo è un terreno instabile, per me). Corretto?

Però, e qui viene il bello, per evitare di impazzire vorrei che tutti i comandi fossero "caricati" in un unico array di oggetti dal quale poter attingere liberamente in avanti, all'indietro e, se necessario, anche da posizioni "qualsiasi", durante l'esecuzione in risposta all'input dell'utente. Orbene, tempo addietro lessi in merito al polimorfismo, in realtà senza capirci gran che, ed ora mi piacerebbe provare a fare qualcosa che lo utilizzi perché MI SEMBRA possa darmi quel che mi serve.

Il ragionamento che faccio è: creo un array di puntatori ad oggetti di classe base COMANDO, tutti NULL, quindi lo "popolo" creando dinamicamente oggetti delle varie classi derivate ricavandone il tipo dal file che contiene le etichette (e altri dati).

A quel punto, "estraendo" un puntatore qualsiasi dall'array non dovrei aver bisogno di preoccuparmi di quale tipo di comando sia, bensì potrei chiamare un metodo (virtuale?) Esegui() che sarebbe implementato, con le varianti del caso, nelle singole classi che definiscono i diversi comandi... spero d'essere riuscito a spiegarmi...

Può funzionare? Sono fuori strada? Il polimorfismo non c'entra nulla?
Per il momento, prima di cimentarmi con l'impresa in grande stile (per le mie possibilità), sto facendo un programma di prova molto scarnificato che SEMBRA fare quel che ho tentato di descrivere. La classe base, in questo caso, si chiama BASE e le classi derivate DER1, DER2 e DER3. Il metodo generico non è Esegui(), bensì Mostra() e non fa cose molto utili -- si limita a evidenziare in quale classe alberga il corpo della funzione chiamata. Anche seguendo il flusso dell'esecuzione tramite un debugger sembra sia tutto in ordine, ma preferisco chiedere a chi ne sa più di me, per evitare di partire col piede sbagliato.

Il codice è su due file.

main.cpp

Codice sorgente - presumibilmente C++

  1. #include <cstdio>
  2. #include <cstdlib>
  3. #include <cstring>
  4.  
  5. #include "polimorfico.h"
  6.  
  7. const char kStrNomeFile[]             = "tipi.txt";
  8. const char kStrErrFileNonAperto[]     = "file non aperto\n";
  9. const char kStrErrPuntatoreNull[]     = "puntatore NULL\n" ;
  10. const char kStrErrNoAllocazione[]     = "memoria non allocata\n";
  11. const char kStrErrNoOggetti[]         = "oggetti non creati\n";
  12. const char kStrErrTipiNonRilevabili[] = "tipi non rilevabili\n";
  13. const char kStrErrTipiNonCaricati[]   = "tipi non caricati\n";
  14. const char kStrErrTipoNonValido[]     = "tipo non valido";
  15.  
  16. int conta_tipi_nel_file( const char *nf );
  17. int *carica_tipi_dal_file( const char *nf, int qt );
  18. BASE **crea_oggetti_di_vario_tipo( int *tipi, int qTipi );
  19. BASE **distruggi_oggetti( BASE **ptrs, int qPtrs );
  20.  
  21. int main() {
  22.     int qTipi = conta_tipi_nel_file( kStrNomeFile );
  23.  
  24.     if( qTipi < 0 ) { // errore
  25.         puts( kStrErrTipiNonRilevabili );
  26.         return 0;
  27.     }
  28.  
  29.     int *tipi = carica_tipi_dal_file( kStrNomeFile, qTipi );
  30.  
  31.     if( NULL == tipi ) { // errore
  32.         puts( kStrErrTipiNonCaricati );
  33.         return 0;
  34.     }
  35.  
  36.     BASE **ptrs = crea_oggetti_di_vario_tipo( tipi, qTipi );
  37.  
  38.     delete[] tipi; tipi = NULL; // non serve piu'
  39.  
  40.     if( NULL != ptrs ) {
  41.         for( int i=0; i<qTipi; ++i ) {
  42.             if( NULL==ptrs[i] ) continue;
  43.             printf( "\noggetto n. %d:\n", i+1 );
  44.             ptrs[i]->Mostra();
  45.         }
  46.  
  47.         ptrs = distruggi_oggetti( ptrs, qTipi );
  48.     }
  49.     else { // errore
  50.         puts( kStrErrNoOggetti );
  51.     }
  52.  
  53.     return 0;
  54. }
  55.  
  56. /*==============================================================================
  57. Apre in modalita' testuale il file identificato dalla stringa puntata da nf (nf:
  58. nome file) e ne scorre il contenuto contando la quantita' di occorrenze delle
  59. etichette di tipo valide. Le etichette valide sono quelle elencate nell'array
  60. di stringhe kStrNomiTipi, nel file polimorfico.h. Le etichette non valide sono
  61. ignorate.
  62. Restituisce la quantita' delle etichette di tipo valide trovate. In caso di
  63. errore presenta in console una stringa per descrivere il problema verificatosi
  64. e restituisce -1.
  65. ==============================================================================*/
  66.  
  67. int conta_tipi_nel_file( const char *nf ) {
  68.     if( NULL==nf ) { // errore
  69.         puts( kStrErrPuntatoreNull );
  70.         return -1;
  71.     }
  72.  
  73.     FILE *f = fopen( nf, "r" );
  74.  
  75.     if( !f ) { // errore
  76.         puts( kStrErrFileNonAperto );
  77.         return -1;
  78.     }
  79.  
  80.     int i, qt = 0;
  81.     char t[16];
  82.  
  83.     while( 1 == fscanf(f,"%s",t) )
  84.         for( i=tipo_base; i<quantita_tipi; ++i )
  85.             if( BASE::TipoDaStringa(t)!=tipo_non_valido )
  86.                 { ++qt; break; }
  87.  
  88.     fclose( f );
  89.     return qt;
  90. }
  91.  
  92. /*==============================================================================
  93. Apre in modalita' testuale il file identificato dalla stringa puntata da nf (nf:
  94. nome file) e ne scorre il contenuto alla ricerca di qt occorrenze di etichette
  95. di tipo valide (si presuppone che il file contenga effettivamente qt etichette
  96. valide). Le etichette valide sono quelle elencate nell'array di stringhe
  97. kStrNomiTipi, nel file polimorfico.h. Le etichette non valide sono ignorate.
  98. Restituisce un array di int allocato dinamicamente con new che contiene il
  99. codice di tipo corrispondente alle etichette di tipo valide trovate, come
  100. elencato nell'enumerazione in testa al file polimorfico.h. In caso di errore
  101. presenta in console una stringa per descrivere il problema verificatosi e
  102. restituisce NULL.
  103. ==============================================================================*/
  104.  
  105. int *carica_tipi_dal_file( const char *nf, int qt ) {
  106.     if( NULL==nf ) { puts( kStrErrPuntatoreNull ); return NULL; }
  107.  
  108.     int *tipi = NULL;
  109.  
  110.     try {
  111.         tipi = new int[qt];
  112.     } catch( ... ) {
  113.         puts( kStrErrNoAllocazione );
  114.         return NULL;
  115.     }
  116.  
  117.     FILE *f = fopen( nf, "r" );
  118.  
  119.     if( NULL==f ) { // errore
  120.         puts( kStrErrFileNonAperto );
  121.         delete[] tipi;
  122.         return NULL;
  123.     }
  124.  
  125.     int i, it;
  126.     char t[16];
  127.  
  128.     for( it=0, i=0; it<qt; ++i ) {
  129.         if( 1 == fscanf(f,"%s",t) ) {
  130.             tipi[it] = BASE::TipoDaStringa( t );
  131.             it += tipi[it]!=tipo_non_valido;
  132.         } else break;
  133.     }
  134.  
  135.     fclose( f );
  136.  
  137.     if( it!=qt ) {  // errore
  138.         delete[] tipi;
  139.         tipi = NULL;
  140.     }
  141.  
  142.     return tipi;
  143. }
  144.  
  145. /*==============================================================================
  146. Crea qTipi oggetti del tipo indicato dai codici contenuti nell'array di int tipi
  147. e ne restituisce i puntatori in un array di puntatori a oggetti di tipo BASE
  148. allocato dinamicamente tramite new. Qualora l'array tipi contenesse codici non
  149. validi, alcuni puntatori dell'array restituito potrebbero essere NULL.
  150. In caso d'errore restituisce NULL.
  151. ==============================================================================*/
  152.  
  153. BASE **crea_oggetti_di_vario_tipo( int *tipi, int qTipi ) {
  154.     if( NULL==tipi ) { // errore
  155.         puts( kStrErrPuntatoreNull );
  156.         return NULL;
  157.     }
  158.  
  159.     BASE **oggetti = NULL;
  160.  
  161.     try {
  162.         oggetti = new BASE*[qTipi];
  163.         memset( oggetti, 0, sizeof(*oggetti)*qTipi );
  164.  
  165.         for( int i=0; i<qTipi; ++i ) {
  166.             switch( tipi[i] ) {
  167.                 case tipo_base: oggetti[i] = new BASE; break;
  168.                 case tipo_der1: oggetti[i] = new DER1; break;
  169.                 case tipo_der2: oggetti[i] = new DER2; break;
  170.                 case tipo_der3: oggetti[i] = new DER3; break;
  171.                 default: puts( kStrErrTipoNonValido ); // errore, non alloca
  172.             }
  173.         }
  174.     } catch( ... ) {
  175.         oggetti = distruggi_oggetti( oggetti, qTipi );
  176.     }
  177.  
  178.     return oggetti;
  179. }
  180.  
  181. /*==============================================================================
  182. Riceve un array di puntatori allocato dinamicamente per mezzo di new contenente
  183. puntatori a oggetti di tipo BASE, anch'essi allocati dinamicamente per mezzo di
  184. new. Distrugge uno ad uno con delete i puntatori agli oggetti, quindi distrugge
  185. con delete anche l'array dei puntatori.
  186. Restituisce NULL.
  187. ==============================================================================*/
  188.  
  189. BASE **distruggi_oggetti( BASE **ptrs, int qPtrs ) {
  190.     if( NULL!=ptrs ) {
  191.         for( int i=qPtrs-1; i>=0; --i ) {
  192.             if( NULL != ptrs[i] ) {
  193.                 delete ptrs[i];
  194.                 ptrs[i] = NULL;
  195.             }
  196.         }
  197.  
  198.         delete[] ptrs;
  199.         ptrs = NULL;
  200.     }
  201.  
  202.     return ptrs;
  203. }



polimorfico.h

Codice sorgente - presumibilmente C++

  1. #ifndef POLIMORFICO_H
  2. #define POLIMORFICO_H
  3.  
  4. #include <cstdio>
  5.  
  6. enum {
  7.     tipo_non_valido = -1,
  8.     tipo_base,
  9.     tipo_der1,
  10.     tipo_der2,
  11.     tipo_der3,
  12.     quantita_tipi
  13. };
  14.  
  15. const char kStrNomiTipi[quantita_tipi][5] = {
  16.     "base",
  17.     "der1",
  18.     "der2",
  19.     "der3"
  20. };
  21.  
  22.  
  23. class BASE {
  24.     public:
  25.         BASE() { tipo = tipo_base; }
  26.         virtual ~BASE() {;}
  27.  
  28.         void Tipo( int t ) { tipo = t>=0&&t<quantita_tipi?t:tipo_base; }
  29.         int Tipo( void ) { return tipo; }
  30.  
  31.         virtual void Mostra( void ) const
  32.             { printf( "dalla classe base: tipo %s\n", kStrNomiTipi[tipo] ); }
  33.  
  34.         static int TipoDaStringa( const char *s );
  35.  
  36.     protected:
  37.  
  38.     private:
  39.         int tipo;
  40. };
  41.  
  42. int BASE::TipoDaStringa( const char *s ) {
  43.     for( int i=tipo_base; i<quantita_tipi; ++i )
  44.         if( 0 == strcmp(s,kStrNomiTipi[i]) ) return i;
  45.  
  46.     return tipo_non_valido;
  47. }
  48.  
  49. class DER1 : public BASE {
  50.     public:
  51.         DER1() { Tipo( tipo_der1 ); }
  52.         virtual ~DER1() {;}
  53.  
  54.         void Mostra( void ) const {
  55.             puts("dalla classe derivata 1");
  56.             BASE::Mostra();
  57.         }
  58.  
  59.     protected:
  60.  
  61.     private:
  62. };
  63.  
  64. class DER2 : public BASE {
  65.     public:
  66.         DER2() { Tipo( tipo_der2 ); }
  67.         virtual ~DER2() {;}
  68.  
  69.         void Mostra( void ) const {
  70.             puts("dalla classe derivata 2");
  71.             BASE::Mostra();
  72.         }
  73.  
  74.     protected:
  75.  
  76.     private:
  77. };
  78.  
  79. class DER3 : public BASE {
  80.     public:
  81.         DER3() { Tipo( tipo_der3 ); }
  82.         virtual ~DER3() {;}
  83.  
  84.         void Mostra( void ) const {
  85.             puts("dalla classe derivata 3");
  86.             BASE::Mostra();
  87.         }
  88.  
  89.     protected:
  90.  
  91.     private:
  92. };
  93.  
  94. #endif // POLIMORFICO_H



Il file dei dati, per le prove, per ora solo con le etichette testuali che identificano i tipi. Nel caso reale conto di usare un csv con una struttura più articolata.

tipi.txt

Codice sorgente - presumibilmente C/C++

  1. base
  2. base
  3. der1
  4. der1
  5. fake
  6.  
  7. der3
  8.  
  9. der1
  10. der2



ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 16:44
Martedì, 11/12/2018
Crei una classe Comando che ha un metodo virtuale puro esegui() e inserisci gli elementi in std::vector<unique_ptr<Comando>> inutile reinventare un array dinamico di puntatori!

PM Quote
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 20:24
Martedì, 11/12/2018
Ho cercato il significato di "virtuale puro", ed ho scoperto che fa riferimento a metodi "= 0;" che rendono impossibile creare oggetti della classe che li comprende (il che ha perfettamente senso, in effetti: chiamando il metodo in questione, se ho ben capito, finiresti per chiamare un puntatore nullo). Io invece avrei bisogno un sistema "misto" nel quale una parte dei compiti sono comuni a tutti gli oggetti delle classi derivate e possono essere svolti dal metodo nella classe base, mentre solo alcuni dettagli o compiti accessori sarebbero diversi per ogni classe derivata. In un contesto simile, se ho ben interpretato quel che ho letto, un metodo "puro" renderebbe impraticabile questo tipo di caratteristica. Però, come ho detto, su quest'argomento non ho ALCUNA certezza (se no non chiederei). Che dici, son fuori strada?

Conosco poco o niente std, per cui usarlo mi renderebbe ancor più "estraneo" l'ambiente nel quale muovermi col rischio di portarmi a perdere senza più riuscire a capire da che parte arrivano gli eventuali errori. Non sono abbastanza "elastico" (ed esperto) per affrontare troppe novità in una sola volta. E' un difetto del quale sono fortunatamente consapevole. Ora vorrei capire l'altra questione, senza "interferenze".

Ultima modifica effettuata da AldoBaldo il 11/12/2018 alle 20:28


ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 23:01
Martedì, 11/12/2018
Considera una classe Comando con un esegui() virtuale puro...
Crei una classe CopiaFile che eredita da Comando e definisci esegui() in CopiaFile, e che ha un metodo controlla() che non è dichiarato in Comando, quando tu chiami quel metodo su un oggetto la vtable viene usata per decidere che va chiamata la funzione esegui di CopiaFile, che farà effettivamente la copia di un file.

Se avrai bisogno di chiamare un metodo definito in una sola classe della tua gerarchia (ad esempio controlla()) utilizzerai auto cp = dynamic_cast<CopiaFile*>(cmd); con cmd che è una istanza di Comando. Se il risultato del dynamic_cast è non-nullo l'oggetto cmd è effettivente una istanza di CopiaFile e a quel punto è sicuro fare cp->controlla()

PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 2:29
Mercoledì, 12/12/2018
Testo quotato

Postato originariamente da AldoBaldo:
Ho cercato il significato di "virtuale puro", ed ho scoperto che fa riferimento a metodi "= 0;" che rendono impossibile creare oggetti della classe che li comprende (il che ha perfettamente senso, in effetti: chiamando il metodo in questione, se ho ben capito, finiresti per chiamare un puntatore nullo). Io invece avrei bisogno un sistema "misto" nel quale una parte dei compiti sono comuni a tutti gli oggetti delle classi derivate e possono essere svolti dal metodo nella classe base, mentre solo alcuni dettagli o compiti accessori sarebbero diversi per ogni classe derivata. In un contesto simile, se ho ben interpretato quel che ho letto, un metodo "puro" renderebbe impraticabile questo tipo di caratteristica. Però, come ho detto, su quest'argomento non ho ALCUNA certezza (se no non chiederei). Che dici, son fuori strada?



Non credo di aver avet capito cosa intendi....

Un metodo virtuale puro sta ad jndicare che il metodo è presente in tutte le sottoclassi della gerarchia, una classe è istanziabile solo se fornisce una implementazione dello stesso, e quindi chiamando quel metodo eseguirai la funzione che ti aspetti, perché la valutazione viene eseguita a runtime, e non dal compilatore.

PM Quote
Avatar
Mikelius (Member)
Expert


Messaggi: 525
Iscritto: 14/04/2017

Segnala al moderatore
Postato alle 12:34
Mercoledì, 12/12/2018
Prova a vedere le classi astratte (utilizzano i metodi virtuali puri)

In pratica crei una classe di "base" , che , è vero che non puoi istanziare, ma puoi ereditarla nelle altre classi che userai.
In essa scrivi solo i metodi comuni. Nelle altre classi erediti la classe "base", ed scrivi i relativi metodi differenti.
Al limite puoi fare un "override" del metodo . Cioè cambi il funzionamento del metodo della classe astratta.
In c# funziona, e a quanto so la cosa è fattibile (con leggere differenze credo)

Ecco un link alle classi astratte.
https://www.html.it/pag/15524/classi-astratte-override-e-fi ...

PM Quote
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 21:51
Mercoledì, 12/12/2018
Vi ringrazio di cuore per le risposte, però devo confessare che le ho lette con fatica riuscendo a comprendere solo una minima parte di quella di Mikelius (TheDarkJuster, ho letto e riletto, ma non ci arrivo). Evidentemente è un passo troppo lungo per le mie gambe, oppure è uno di quei casi nei quali è più semplice fare che spiegare... Proverò a sondare ancora un po' il terreno con i miei test, poi deciderò se procedere con l'idea di usare queste tecniche o di lasciar perdere del tutto il progetto (almeno in questa forma). Che stoccata, però! :(


ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Avatar
TheDarkJuster (Member)
Guru^2


Messaggi: 1620
Iscritto: 27/09/2013

Segnala al moderatore
Postato alle 23:25
Mercoledì, 12/12/2018
Questi argomenti sono da affrontare con un ottimo libro, o un ottimo professore....

Comunque domani dovrei riuscire a scriverti un pezzetto di codice tanto per farti capire ciò che intendo.

Codice sorgente - presumibilmente C#

  1. class Comando {
  2. public:
  3.     Comando() = delete;
  4.     Comando(const Comando&) = delete;
  5.     Comando& operator=(const Comando&) = delete;
  6.     virtual ~Comando() = default;
  7.     virtual int esegui() const = 0;
  8. };
  9.  
  10. class CopiaFile : public Comando {
  11.     string src, dest;
  12. public:
  13.     CopiaFile(const CopiaFile& a) : src(a.src), dest(a.dest) {}
  14.     CopiaFile(string src, string dest) : src(src), dest(dest) {}
  15.     virtual ~CopiaFile() = default;
  16.     virtual int esegui() const {
  17.         return eseguiConValoreDiRitorno(string("cp ") + src + " " + dest);
  18.     }
  19. }
  20.  
  21. class SpostaFile : public Comando {
  22.    // uguale a CopiaFile, con un diverso esegui() che ha mv al posto di cp
  23. }
  24.  
  25. vector<Comando*> comandi;
  26. comandi.push_back(new CopiaFile("a", "b.new"));
  27. comandi.push_back(new CopiaFile("c", "d.new"));
  28. for (auto cmd : comandi) {
  29.     cmd->esegui(); // Qui viene eseguita la funzione esegui() a seconda del tipo "dinamico" dell'elemento corrente, NON esegui() di Comando, anche se non fosse stata puramente virtuale
  30. }


Ultima modifica effettuata da TheDarkJuster il 13/12/2018 alle 15:58
PM Quote
Avatar
AldoBaldo (Member)
Guru


Messaggi: 699
Iscritto: 08/01/2015

Segnala al moderatore
Postato alle 17:44
Giovedì, 13/12/2018
TheDarkJuster, ti meriti di nuovo un ringraziamento bello grosso, però sono troppe le nuove informazioni, troppi i costrutti e i concetti che mi proponi in una volta, non riesco a seguirti. E' un'impresa disperata.

P.S. Il libro sul C++ ce l'ho, anzi, ne ho tre (due cartacei, uno in formato elettronico) e li consulto regolarmente, ma risalgono agli anni '90 e su nessuno di essi compaiono alcune delle "formule" che ci sono nel tuo codice.


ATTENZIONE! Sono un hobbista e l'affidabilità delle mie conoscenze informatiche è molto limitata. Non prendere come esempio il codice che scrivo, perché non ho alcuna formazione accademica e rischieresti di apprendere pratiche controproducenti.
PM Quote
Pagine: [ 1 2 ] Precedente | Prossimo