[C++]Classi astratte e puntatori ad esse

di il
9 risposte

[C++]Classi astratte e puntatori ad esse

Salve, ho un problema riguardante l'utilizzo di un puntatore ad una classe astratta.
io ho la classe PartitaCarte che contiene questi membri:
protected:
	Mazzo *mazzo; // mazzo con cui giocare
	Tavolo *tavolo; // tavolo della partita
	GiocatoreCarte *giocatore; // giocatore umano della partita
	vector<IACarte*> ia; // vector contenente i giocatori non umani della partita
le classi Tavolo, GiocatoreCarte e IACarte sono tutte astratte, oltre alla stessa PartitaCarte.
poi ho una classe PartitaBriscola, figlia di PartitaCarte, che, nel costruttore, esegue queste istruzioni:
for (int i = 0; i < ia.size(); i++)
	ia[i] = new IABriscola();

tavolo = new TavoloBriscola(n + 1);
mazzo = new Mazzo(40);
giocatore = new GiocatoreBriscola();
il problema è che successivamente vado a fare l'istruzione
tavolo->setBriscola((*mazzo)[mazzo->getCarteAttuali() - 1]);
ma mi viene generato quest'errore dall'intellisense: class "Tavolo" non include alcun membro "setBriscola".
setBriscola è un metodo definito in TavoloBriscola (non è nemmeno presente in forma virtuale in Tavolo), tuttavia non capisco come mai, nonostante tavolo di PartitaCarte fosse stato allocato in TavoloBriscola, il metodo è ancora "inesistente".
qualcuno sa come ovviare a questo problema?

9 Risposte

  • Re: [C++]Classi astratte e puntatori ad esse

    Il motivo è che tavolo è un puntatore a Tavolo non a TavoloBriscola, per cui ha accesso solo ai metodi definiti in Tavolo.
    Anche se derivi TavoloBriscola da Tavolo questo non significa che i metodi di TavoloBriscola siano trasferiti automaticamente in Tavolo (non si eredita al contrario). L'ereditarietà polimorfa avviene quando una classe base (Tavolo) ha una funzione virtuale che può (deve se virtuale pura) essere ridefinita nella classe derivata.
    In soldoni, la funzione setBriscola dev'essere virtuale di Tavolo e ridefinita (se serve) in TavoloBriscola.
    Ovvio che si perde in generalità (un tavolo di scopone scientifico o ramino potrebbe avere dei metodi diversi), per cui setBriscola è corretto che si trovi in TavoloBriscola, ma ci sono dei metodi (mi riferisco a calcolaPunteggio,reset,trovaVincitore, presumo anche distanceFrom0) che potrebbero essere spostati in Tavolo, resi virtuali e ridefiniti per i diversi tavoli di gioco.
    (Fine teoria)
    Nello specifico, se PartitaBriscola si riferisce a una partita a briscola, e nel suo costruttore ti serve un tavolo di briscola, tu dagli un tavolo di briscola non un tavolo generico. Viceversa se il costruttore di PartitaBriscola acquisisce come parametro un Tavolo generico, allora è sufficiente un dynamic_cast<> (ci dev'essere almeno una funzione virtuale perché dynamic_cast<> funzioni).
    Mi riferisco a uno scenario simile (presupponendo di avere N tavoli di gioco).
    
    PartitaBriscola::PartitaBriscola(Tavolo* table) {
    
        tavolobriscola = dynamic_cast<TavoloBriscola*>(table);
        if (tavolobriscola == nullptr)
            throw std::bad_cast; // tavolo sbagliato.
    
    }
    
    
    P.S. Se hai aggiornato il codice su Google drive, metti il link.
  • Re: [C++]Classi astratte e puntatori ad esse

    Grazie per la risposta.
    il codice è diverso da quello di google drive (ho aggiunto varie classi astratte): click

    mmmh, quindi seguendo ciò che dici, dovrei fare una cosa del genere:
    class PartitaBriscola
    {
    	TavoloBriscola* tavolo;
    	GiocatoreBriscola giocatore;
    	vector<IABriscola> ia; 
    	...
    };
    ma non è una cosa inutile?
    io ho sviluppato la classe PartitaCarte che è una classe astratta che descrive un qualsiasi gioco di carte: il mazzo, il tavolo, il giocatore e i computer.
    se devo andare a ricrearmi questi attributi anche in PartitaBriscola non è uno spreco a questo punto creare la classe astratta PartitaCarte?

    per quanto riguarda l'implementazione di alcuni metodi non sono così sicuro di alcuni.
    calcolaPunteggio penso non sia utile: a uno per esempio le carte non hanno un punteggio, così come a rubamazzo per dire, ergo sarebbe "dannoso" creare questa funzione virtuale pura, in quanto in un ipotetico TavoloUno dovrei ridefinirmi "vuoto" questo metodo.
  • Re: [C++]Classi astratte e puntatori ad esse

    No, PartitaCarte non descrive un qualsiasi gioco di carte, dato che mancano tutti i metodi per ogni gioco di carte. Sono i metodi, non i dati membro a dare un'interfaccia comune e l'unico metodo comune (per il momento) è gioca() (oltre al distruttore che dev'essere virtual).
    Il punto è che stai creando il giocatore e il tavolo internamente alla PartitaBriscola e in quel contesto è inutile riferirsi a classi astratte, tanto vale usare le implementazioni concrete.
    Le classi astratte (che possono avere anche solo il distruttore virtuale), hanno senso in un contesto simile (al netto di controlli vari):
    
    Giocatore* giocatore1 = new GiocatoreBriscola("Ulisse");
    Giocatore* giocatore2 = new GiocatoreScopone("Polifemo");
    
    Tavolo* tavolo1 = new TavoloBriscola;
    Tavolo* tavolo2 = new TavoloScopone;
    
    PartitaCarte* partita = new PartitaBriscola(giocatore1,tavolo1);
    partita->gioca(); // ok giocatore e tavolo da briscola.
    
    partita = new PartitaBriscola(giocatore2,tavolo1);
    partita->gioca(); // No! giocatore2 è di scopone
    
    partita = new PartitaScopone(giocatore2,tavolo1);
    partita->gioca(); // No! tavolo1 è di briscola
    
    partita = new PartitaScopone(giocatore2,tavolo2);
    partita->gioca(); // ok. giocatore2 e tavolo2 da scopone
    
    
    Ovvio che i dati membro vanno spostati da PartitaCarte a PartitaBriscola e PartitaScopone e ognuno sarà specifico per quella classe.
    Spero di essermi spiegato.
    calcolaPunteggio penso non sia utile: a uno per esempio le carte non hanno un punteggio, così come a rubamazzo per dire, ergo sarebbe "dannoso" creare questa funzione virtuale pura, in quanto in un ipotetico TavoloUno dovrei ridefinirmi "vuoto" questo metodo.
    E tu non farla virtuale pura. La rendi virtuale e basta, fornendo un'implementazione di default. Solo le funzioni virtuali pure devono essere forzatamente implementate nelle classi derivate (il distruttore è un caso speciale).
  • Re: [C++]Classi astratte e puntatori ad esse

    shodan ha scritto:


    No, PartitaCarte non descrive un qualsiasi gioco di carte, dato che mancano tutti i metodi per ogni gioco di carte. Sono i metodi, non i dati membro a dare un'interfaccia comune e l'unico metodo comune (per il momento) è gioca() (oltre al distruttore che dev'essere virtual).
    Il punto è che stai creando il giocatore e il tavolo internamente alla PartitaBriscola e in quel contesto è inutile riferirsi a classi astratte, tanto vale usare le implementazioni concrete.
    mmmh, capisco, quindi la classe PartitaCarte è più corretto vederla come un'interfaccia, dato che è inutile che abbia attributi "generali", poiché ogni gioco ha i suoi specifici attributi.
    ma questo vale anche per le classi GiocatoreCarte e Tavolo? è sbagliato vedere il primo come un giocatore qualsiasi (che ha in mano un insieme di carte) e il secondo come l'insieme delle carte sul tavolo di gioco? sarebbe opportuno che fossero anche questi delle interfacce?

    shodan ha scritto:


    E tu non farla virtuale pura. La rendi virtuale e basta, fornendo un'implementazione di default. Solo le funzioni virtuali pure devono essere forzatamente implementate nelle classi derivate (il distruttore è un caso speciale).
    sì, penso sia abbastanza saggio. forse sarebbe anche furbo mettere il punteggio come un attributo di GiocatoreCarte: se poi viene creato un giocatore di un gioco senza punteggio (come GiocatoreUno, per esempio), al massimo si tiene sempre il punteggio uguale a 0.
  • Re: [C++]Classi astratte e puntatori ad esse

    Se il post è troppo lungo lo divido in due parti.
    ma questo vale anche per le classi GiocatoreCarte e Tavolo? è sbagliato vedere il primo come un giocatore qualsiasi (che ha in mano un insieme di carte) e il secondo come l'insieme delle carte sul tavolo di gioco? sarebbe opportuno che fossero anche questi delle interfacce?
    I vari giocatori hanno e fanno le stesse cose: un nome, un tot di carte, pescano (ricevono), scartano e hanno le carte in una certa posizione.
    La differenza sta che mentre GiocatoreBriscola ha tre carte in mano e può contare i punti, GiocatoreScopone ne ha dieci e può non contare i punti.
    Tradotto in codice significa che GiocatoreCarte è "quasi" un giocatore qualsiasi, ma che in alcuni tipi di gioco deve sapersi adattare.
    In codice avresti qualcosa del genere:
    
    class GiocatoreCarte {
        protected:
            string nome;
            vector<Carta> mano;
            int indiceVuoto;
        public:
            virtual ~GiocatoreCarte() = 0 {}
            virtual void addPunteggio(int p) { /* vuota per i giocatori che non aggiungono punti */ }
            virtual int getPunteggio() { return 0; /* per i giocatori che non restituiscono punteggio */ }
            
            int carteInMano() // quante carte ha il giocatore.
            bool isVoid();
            void pesca(const Carta& carta);
            Carta at(int index); // vedi sotto  
            Carta scarta(int index);
    };
    
    class GiocatoreBriscola : public GiocatoreCarte { // conta i punti
        protected:
            // i dati principali li ricevo dalla classe base
            int punteggio;
        public:
            GiocatoreBriscola(string nome) { /* preparo quel che serve per una mano di 3 carte */ ; }
            ~GiocatoreBriscola() { /* quel che serve */ }
            void addPunteggio(int p) { /* quel che serve */ }
            int getPunteggio() { /* quel che serve */ }
    };
    
    class GiocatoreScopone : public GiocatoreCarte { // non conta i punti
        protected:
            // i dati principali li ricevo dalla classe base
        public:
            GiocatoreScopone (string nome) { /* preparo quel che serve per una mano di 10 carte */ ; }
            ~GiocatoreScopone () { /* quel che serve */ }
    };
    
    void daiCarteA(GiocatoreCarte* g) {
        for(int i = 0; i < g->carteInMano(); i++) {
        	Carta c; 
        	g->pesca(c);
        }
    }
    
    void controllo(GiocatoreCarte* g) {
        g->isVoid();
        g->at(4);
        g->scarta(2);
        g->pesca(/* quel che è */);
        g->addPunteggio(10);
        g->getPunteggio();
    }
    
    int main() {
    
        GiocatoreCarte* g1 = new GiocatoreBriscola("ulisse");
        GiocatoreCarte* g2 = new GiocatoreScopone("polifemo");
        
        daiCarteA(g1);
        daiCarteA(g2);
    
        controllo(g1);
        controllo(g2);
    
        delete g1;
        delete g2;
    }
    
    Come vedi le funzioni daiCarteA e controllo ricevono un GiocatoreCarte e usano gli stessi metodi. Per cui GiocatoreCarte può essere usata come classe base polimorfa (non la chiamo interfaccia perché da quel che ho capito intendiamo cose un attimo diverse).
    Quel "vedi sotto" si riferisce al fatto che se usi i puntatori, gli operatori non sono invocati. In questo caso si usa una funzione.

    Nel caso di Tavolo continuo dopo cena.
  • Re: [C++]Classi astratte e puntatori ad esse

    Nel caso di Tavolo la cosa è più complicata, in quanto i tavoli di gioco hanno regole diverse. Ad esempio TavoloBriscola ha setBriscola() mentre un TavoloScopone non può avere quel metodo. Entrambi avranno un metodo reset() che può svolgere un algoritmo di reset diverso da tavolo a tavolo. Entrambi i tavoli avranno un trovaVincitore() il cui algoritmo dipende dal tavolo. Entrambi hanno un metodo trovaVincitore(), entrambi avranno un metodo getCarteAttuali.
    Per cui questi metodi saranno parte dell'interfaccia di Tavolo, ma non sono certi sufficienti per definire una briscola invece di una partita a scopone.

    Una nota su vector.
    reserve() riserva memoria, ma non costruisce locazioni nel vector, quindi non puoi accedere con l'operatore[] ai vari elementi per inserirli, ma devi usare push_back; per evitare problemi usa direttamente resize(). Nei cicli for usa size() non capacity() per sapere quanti elementi sono contenuti.
  • Re: [C++]Classi astratte e puntatori ad esse

    Prima di scrivere il codice, devi avere chiaro come mettere in piedi tutto il marchingegno.

    A livello di classe astratta, o di interfaccia, devi ragionare in modo il piu' generico possibile.

    Per fare questo devi avere sotto mano piu' esempi e cercare di raccogliere a fattor comune le funzionalita' comuni, e identificare quali siano le funzionalita' che richiedono specializzazione.

    Per il gioco delle carte, come esempi puoi prendere: poker, scala 40, briscola, ramino, i giochi solitari (perche' no!), e qualunque altro gioco ti venga in mente.

    Da questo puoi dedurre che:
    c'e' un certo numero di giocatori, uno o piu'
    c'e' un mazzo di carte. Quante? Dipende
    ogni mazzo ha un certo numero di colori (magari solo uno), di segni (quadri, cuori, fiori, picche, oppure danari, bastoni, spade, coppe, ecc), e di carte (10, 13, ...)
    ogni giocatore deve avere un certo numero di carte iniziale
    ci possono essere 0 o piu' carte sul tavolo
    ogni giocatore deve scegliere una o piu' carte in base a qualche euristica, legata alle carte che ha in mano, a quelle sul tavolo e a quelle che sono gia' uscite, ...
    il giocatore fa la sua giocata
    si valuta se ha vinto, perso, o non e' ancora successo nulla
    ...
    c'e' un turno da rispettare

    ecc

    Poi, dovrai fare le implementazioni specifiche per ogni gioco: il tipo e umero di carte, quante carte assegnare ad ogni giocatore, come scegliere la carta o le carte, come valutare la vincita ...

    Immaginare tutto questo, in modo corretto, e come implementarlo, non e' una cosa che si impara in 5 minuti
  • Re: [C++]Classi astratte e puntatori ad esse

    shodan ha scritto:



    I vari giocatori hanno e fanno le stesse cose: un nome, un tot di carte, pescano (ricevono), scartano e hanno le carte in una certa posizione.
    La differenza sta che mentre GiocatoreBriscola ha tre carte in mano e può contare i punti, GiocatoreScopone ne ha dieci e può non contare i punti.
    Tradotto in codice significa che GiocatoreCarte è "quasi" un giocatore qualsiasi, ma che in alcuni tipi di gioco deve sapersi adattare.
    sì, forse mi sono spiegato un po' male. intendevo dire che GiocatoreCarte è "l'insieme" di tutti i metodi e attributi (appunto, le carte che ha in mano) che sono in comune con ogni gioco di carte. magari ce ne sono tanti altri, ma non conoscendo milioni di giochi di carte, ciò che tutti i giocatori hanno in comune ho pensato fosse l'insieme di carte che hanno in mano, il nome e l'indice vuoto, che è un piccolo escamotage per non dover fare la ricerca della posizione libera ogni volta che il giocatore deve aggiungere una carta alla propria mano.
    il punteggio invece credo sia meglio vederlo solo in GiocatoreBriscola o GiocatoreScopone (e tutti gli altri giochi in cui c'è un punteggio), dato che un GiocatoreUno, per dire, non lo ha. magari si potrebbe comunque mettere il punteggio in GiocatoreCarte per un qualche escamotage. a rubamazzo non c'è il punteggio per esempio, ma per fare una "furbata" si può mettere che il giocatore con più carte ha punteggio 1 e gli altri con punteggio 0. ma per ora, penso che terrò il punteggio come attributo di un giocatore di un gioco specifico.

    shodan ha scritto:


    Come vedi le funzioni daiCarteA e controllo ricevono un GiocatoreCarte e usano gli stessi metodi. Per cui GiocatoreCarte può essere usata come classe base polimorfa (non la chiamo interfaccia perché da quel che ho capito intendiamo cose un attimo diverse).
    Quel "vedi sotto" si riferisce al fatto che se usi i puntatori, gli operatori non sono invocati. In questo caso si usa una funzione.
    per interfaccia intendo la parolina di java e c#, ovvero una classe astratta senza attributi e contenente solo dei metodi virtuali, ma mi sa che usarla in c++ è un po' azzardato dato che le interfacce non esistono grazie all'ereditarietà multipla.
    comunque ho capito l'utilità dei puntatori alle classi astratte, grazie.

    shodan ha scritto:


    Nel caso di Tavolo la cosa è più complicata, in quanto i tavoli di gioco hanno regole diverse. Ad esempio TavoloBriscola ha setBriscola() mentre un TavoloScopone non può avere quel metodo. Entrambi avranno un metodo reset() che può svolgere un algoritmo di reset diverso da tavolo a tavolo. Entrambi i tavoli avranno un trovaVincitore() il cui algoritmo dipende dal tavolo. Entrambi hanno un metodo trovaVincitore(), entrambi avranno un metodo getCarteAttuali.
    Per cui questi metodi saranno parte dell'interfaccia di Tavolo, ma non sono certi sufficienti per definire una briscola invece di una partita a scopone.
    certo, come intendevo fare io (anche se forse mi sono spiegato male di nuovo). il tavolo per me è l'insieme di tutte quelle carte che si trovano appunto sul tavolo: un giocatore che scarta una carta, la briscola di una partita, le 5 carte del texas holdem, le carte in mezzo a scopa/rubamazzo, sono tutte carte che vengono memorizzate nel vector di Tavolo; poi giustamente, come dici tu, ogni tavolo ha metodi specifici per realizzare quel determinato gioco.

    shodan ha scritto:


    Una nota su vector.
    reserve() riserva memoria, ma non costruisce locazioni nel vector, quindi non puoi accedere con l'operatore[] ai vari elementi per inserirli, ma devi usare push_back; per evitare problemi usa direttamente resize(). Nei cicli for usa size() non capacity() per sapere quanti elementi sono contenuti.
    sì, hai ragione. reserve l'ho usato nei costruttori con parametri per dare la capacità iniziale al vector.
    vedrò di dare un'aggiustata al codice, sto già iniziando a portarlo in c# per dargli una gui decente

    migliorabile ha scritto:


    Prima di scrivere il codice, devi avere chiaro come mettere in piedi tutto il marchingegno.

    A livello di classe astratta, o di interfaccia, devi ragionare in modo il piu' generico possibile.

    Per fare questo devi avere sotto mano piu' esempi e cercare di raccogliere a fattor comune le funzionalita' comuni, e identificare quali siano le funzionalita' che richiedono specializzazione.

    Per il gioco delle carte, come esempi puoi prendere: poker, scala 40, briscola, ramino, i giochi solitari (perche' no!), e qualunque altro gioco ti venga in mente.

    Da questo puoi dedurre che:
    c'e' un certo numero di giocatori, uno o piu'
    c'e' un mazzo di carte. Quante? Dipende
    ogni mazzo ha un certo numero di colori (magari solo uno), di segni (quadri, cuori, fiori, picche, oppure danari, bastoni, spade, coppe, ecc), e di carte (10, 13, ...)
    ogni giocatore deve avere un certo numero di carte iniziale
    ci possono essere 0 o piu' carte sul tavolo
    ogni giocatore deve scegliere una o piu' carte in base a qualche euristica, legata alle carte che ha in mano, a quelle sul tavolo e a quelle che sono gia' uscite, ...
    il giocatore fa la sua giocata
    si valuta se ha vinto, perso, o non e' ancora successo nulla
    ...
    c'e' un turno da rispettare

    ecc

    Poi, dovrai fare le implementazioni specifiche per ogni gioco: il tipo e umero di carte, quante carte assegnare ad ogni giocatore, come scegliere la carta o le carte, come valutare la vincita ...

    Immaginare tutto questo, in modo corretto, e come implementarlo, non e' una cosa che si impara in 5 minuti
    inizialmente il progetto era di realizzare un gioco a piacere, o single player (contro n computer) o multiplayer (senza connettersi a nessuna rete, multiplayer locale insomma) e ho avuto varie "illuminazioni" durante il corso del progetto.
    subito mi era difficile vedere una classe "partita", dato che mi sembrava strano sentire "oggetto partita" (proprio dalle parole, quando mai la partita, nel mondo reale, si può definire "oggetto"?), poi dopo sono riuscito a implementarla (ed è molto meglio che usare un solo main).
    poi ho avuto anche altre idee: all'inizio ero portato al farlo multiplayer con giocatori o computer a scelta, ma dopo ho pensato alla realtà: a cosa serve un multiplayer locale? a niente, è impossibile giocare a briscola localmente, ecco quindi che ho definito il giocatore come unico e il resto erano tutti dei computer (contenuti in un vector).
  • Re: [C++]Classi astratte e puntatori ad esse

    il punteggio invece credo sia meglio vederlo solo in GiocatoreBriscola o GiocatoreScopone (e tutti gli altri giochi in cui c'è un punteggio), dato che un GiocatoreUno, per dire, non lo ha. magari si potrebbe comunque mettere il punteggio in GiocatoreCarte per un qualche escamotage. a rubamazzo non c'è il punteggio per esempio, ma per fare una "furbata" si può mettere che il giocatore con più carte ha punteggio 1 e gli altri con punteggio 0. ma per ora, penso che terrò il punteggio come attributo di un giocatore di un gioco specifico.
    Però così perdi in generalità e lo scopo delle interfaccie è proprio la generalità. Se un giocatore non prevede punteggio, lasci la funzione virtuale vuota o restituisci zero. Le interfaccie servono per scenari simili alla funzione controllo che ho scritto io.
    Ti immagini dover fare dei distinguo per ogni tipologia di giocatore a seconda che abbia o no dei punti? Finché sono due o tre magari la cosa è fattibile, ma se diventano 10, 20, 30?
    per interfaccia intendo la parolina di java e c#, ovvero una classe astratta senza attributi e contenente solo dei metodi virtuali, ma mi sa che usarla in c++ è un po' azzardato dato che le interfacce non esistono grazie all'ereditarietà multipla.
    Ok, almeno ci intendiamo sui termini. In C++ manca la definizione precisa di interfaccia, cosa che Java e C# invece possiedono, ma l'ereditarietà multipla non è il problema (in questo caso almeno).
Devi accedere o registrarti per scrivere nel forum
9 risposte