11/12/18 12:18
AldoBaldo
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
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
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
#include <cstdio> #include <cstdlib> #include <cstring> #include "polimorfico.h" const char kStrNomeFile[] = "tipi.txt"; const char kStrErrFileNonAperto[] = "file non aperto\n"; const char kStrErrPuntatoreNull[] = "puntatore NULL\n" ; const char kStrErrNoAllocazione[] = "memoria non allocata\n"; const char kStrErrNoOggetti[] = "oggetti non creati\n"; const char kStrErrTipiNonRilevabili[] = "tipi non rilevabili\n"; const char kStrErrTipiNonCaricati[] = "tipi non caricati\n"; const char kStrErrTipoNonValido[] = "tipo non valido"; int conta_tipi_nel_file( const char *nf ); int *carica_tipi_dal_file( const char *nf, int qt ); BASE **crea_oggetti_di_vario_tipo( int *tipi, int qTipi ); BASE **distruggi_oggetti( BASE **ptrs, int qPtrs ); int main() { int qTipi = conta_tipi_nel_file( kStrNomeFile ); if( qTipi < 0 ) { // errore puts( kStrErrTipiNonRilevabili ); return 0; } int *tipi = carica_tipi_dal_file( kStrNomeFile, qTipi ); if( NULL == tipi ) { // errore puts( kStrErrTipiNonCaricati ); return 0; } BASE **ptrs = crea_oggetti_di_vario_tipo( tipi, qTipi ); delete[] tipi; tipi = NULL; // non serve piu' if( NULL != ptrs ) { for( int i=0; i<qTipi; ++i ) { if( NULL==ptrs[i] ) continue; printf( "\noggetto n. %d:\n", i+1 ); ptrs[i]->Mostra(); } ptrs = distruggi_oggetti( ptrs, qTipi ); } else { // errore puts( kStrErrNoOggetti ); } return 0; } /*============================================================================== Apre in modalita' testuale il file identificato dalla stringa puntata da nf (nf: nome file) e ne scorre il contenuto contando la quantita' di occorrenze delle etichette di tipo valide. Le etichette valide sono quelle elencate nell'array di stringhe kStrNomiTipi, nel file polimorfico.h. Le etichette non valide sono ignorate. Restituisce la quantita' delle etichette di tipo valide trovate. In caso di errore presenta in console una stringa per descrivere il problema verificatosi e restituisce -1. ==============================================================================*/ int conta_tipi_nel_file( const char *nf ) { if( NULL==nf ) { // errore puts( kStrErrPuntatoreNull ); return -1; } FILE *f = fopen( nf, "r" ); if( !f ) { // errore puts( kStrErrFileNonAperto ); return -1; } int i, qt = 0; char t[16]; while( 1 == fscanf(f,"%s",t) ) for( i=tipo_base; i<quantita_tipi; ++i ) if( BASE::TipoDaStringa(t)!=tipo_non_valido ) { ++qt; break; } fclose( f ); return qt; } /*============================================================================== Apre in modalita' testuale il file identificato dalla stringa puntata da nf (nf: nome file) e ne scorre il contenuto alla ricerca di qt occorrenze di etichette di tipo valide (si presuppone che il file contenga effettivamente qt etichette valide). Le etichette valide sono quelle elencate nell'array di stringhe kStrNomiTipi, nel file polimorfico.h. Le etichette non valide sono ignorate. Restituisce un array di int allocato dinamicamente con new che contiene il codice di tipo corrispondente alle etichette di tipo valide trovate, come elencato nell'enumerazione in testa al file polimorfico.h. In caso di errore presenta in console una stringa per descrivere il problema verificatosi e restituisce NULL. ==============================================================================*/ int *carica_tipi_dal_file( const char *nf, int qt ) { if( NULL==nf ) { puts( kStrErrPuntatoreNull ); return NULL; } int *tipi = NULL; try { tipi = new int[qt]; } catch( ... ) { puts( kStrErrNoAllocazione ); return NULL; } FILE *f = fopen( nf, "r" ); if( NULL==f ) { // errore puts( kStrErrFileNonAperto ); delete[] tipi; return NULL; } int i, it; char t[16]; for( it=0, i=0; it<qt; ++i ) { if( 1 == fscanf(f,"%s",t) ) { tipi[it] = BASE::TipoDaStringa( t ); it += tipi[it]!=tipo_non_valido; } else break; } fclose( f ); if( it!=qt ) { // errore delete[] tipi; tipi = NULL; } return tipi; } /*============================================================================== Crea qTipi oggetti del tipo indicato dai codici contenuti nell'array di int tipi e ne restituisce i puntatori in un array di puntatori a oggetti di tipo BASE allocato dinamicamente tramite new. Qualora l'array tipi contenesse codici non validi, alcuni puntatori dell'array restituito potrebbero essere NULL. In caso d'errore restituisce NULL. ==============================================================================*/ BASE **crea_oggetti_di_vario_tipo( int *tipi, int qTipi ) { if( NULL==tipi ) { // errore puts( kStrErrPuntatoreNull ); return NULL; } BASE **oggetti = NULL; try { oggetti = new BASE*[qTipi]; memset( oggetti, 0, sizeof(*oggetti)*qTipi ); for( int i=0; i<qTipi; ++i ) { switch( tipi[i] ) { case tipo_base: oggetti[i] = new BASE; break; case tipo_der1: oggetti[i] = new DER1; break; case tipo_der2: oggetti[i] = new DER2; break; case tipo_der3: oggetti[i] = new DER3; break; default: puts( kStrErrTipoNonValido ); // errore, non alloca } } } catch( ... ) { oggetti = distruggi_oggetti( oggetti, qTipi ); } return oggetti; } /*============================================================================== Riceve un array di puntatori allocato dinamicamente per mezzo di new contenente puntatori a oggetti di tipo BASE, anch'essi allocati dinamicamente per mezzo di new. Distrugge uno ad uno con delete i puntatori agli oggetti, quindi distrugge con delete anche l'array dei puntatori. Restituisce NULL. ==============================================================================*/ BASE **distruggi_oggetti( BASE **ptrs, int qPtrs ) { if( NULL!=ptrs ) { for( int i=qPtrs-1; i>=0; --i ) { if( NULL != ptrs[i] ) { delete ptrs[i]; ptrs[i] = NULL; } } delete[] ptrs; ptrs = NULL; } return ptrs; }
polimorfico.h
#ifndef POLIMORFICO_H #define POLIMORFICO_H #include <cstdio> enum { tipo_non_valido = -1, tipo_base, tipo_der1, tipo_der2, tipo_der3, quantita_tipi }; const char kStrNomiTipi[quantita_tipi][5] = { "base", "der1", "der2", "der3" }; class BASE { public: BASE() { tipo = tipo_base; } virtual ~BASE() {;} void Tipo( int t ) { tipo = t>=0&&t<quantita_tipi?t:tipo_base; } int Tipo( void ) { return tipo; } virtual void Mostra( void ) const { printf( "dalla classe base: tipo %s\n", kStrNomiTipi[tipo] ); } static int TipoDaStringa( const char *s ); protected: private: int tipo; }; int BASE::TipoDaStringa( const char *s ) { for( int i=tipo_base; i<quantita_tipi; ++i ) if( 0 == strcmp(s,kStrNomiTipi[i]) ) return i; return tipo_non_valido; } class DER1 : public BASE { public: DER1() { Tipo( tipo_der1 ); } virtual ~DER1() {;} void Mostra( void ) const { puts("dalla classe derivata 1"); BASE::Mostra(); } protected: private: }; class DER2 : public BASE { public: DER2() { Tipo( tipo_der2 ); } virtual ~DER2() {;} void Mostra( void ) const { puts("dalla classe derivata 2"); BASE::Mostra(); } protected: private: }; class DER3 : public BASE { public: DER3() { Tipo( tipo_der3 ); } virtual ~DER3() {;} void Mostra( void ) const { puts("dalla classe derivata 3"); BASE::Mostra(); } protected: private: }; #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
base base der1 der1 fake der3 der1 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.