Visualizzazione dei risultati da 1 a 4 su 4
  1. #1
    Utente di HTML.it L'avatar di ing82
    Registrato dal
    Sep 2014
    Messaggi
    177

    [C++] Gestione gerarchia di classi

    Alcuni dubbi su come gestire una gerarchia di strutture dati e classi simile a quella riportata sotto (i dubbi sono nei commenti messi nel codice riportato):


    codice:
    enum Code: unsigned int {Type1, Type2};
    
    struct BaseStruct
    {
      //...
      Code getType() const = 0;
    };
    
    struct BaseStruct1: public BaseStruct
    {
      //...
      Code getType() const override
      {
        return Type1;
      };
    };
    
    struct BaseStruct2: public BaseStruct
    {
      //...
      Code getType() const override
      {
        return Type2;
      };
    };
    
    
    class Base
    {
      public:
        //...metodi vari
        virtual void set(BaseStruct* data) = 0;
        virtual BaseStruct* get() const = 0;
        virtual Code getType() const = 0;
    };
    
    class Derived1: public Base
    {
      public:
        //...metodi vari
        void set(BaseStruct* data) override
        {
          if(data)//verifico che data non sia un nullptr
          {
            if(data->getType() == Type1)//verifico che data sia del tipo corretto
            {
              BaseStruct1* temp = static_cast<BaseStruct1*>(data);
              //quello che serve per settare i dati membro
            }
            else
            {
              //se data e' puntatore valido ma di tipo scorretto, due opzioni
              //1 - lascio che non succeda niente
              //2 - eccezione
            }
          }
          else
          {
            //se arrivo qui, quindi data = nullptr, due opzioni
            //1 - lascio che non succeda niente
            //2 - eccezione
          }
        };
        BaseStruct* get() const override
        {
          BaseStruct* temp = nullptr;
          if(*this)//per fare questo devo aver implementato l'overload dell'operatore bool e quindi aver deciso i casi per cui restituisce true o false
          {
            temp = new BaseStruct1(/*passo quello che serve*/);
          }
          return temp;
        }
        Code getType() const override
        {
          return Type1;
        };
    };
    
    class Derived2: public Base
    {
      public:
        //...metodi vari
        void set(BaseStruct* data) override
        {
          if(data)//verifico che data non sia un nullptr
          {
            if(data->getType() == Type2)//verifico che data sia del tipo corretto
            {
              BaseStruct2* temp = static_cast<BaseStruct2*>(data);
              //quello che serve per settare i dati membro
            }
            else
            {
              //se data e' puntatore valido ma di tipo scorretto, due opzioni
              //1 - lascio che non succeda niente
              //2 - eccezione
            }
          }
          else
          {
            //se arrivo qui, quindi data = nullptr, due opzioni
            //1 - lascio che non succeda niente
            //2 - eccezione
          }
        };
        BaseStruct* get() const override
        {
          BaseStruct* temp = nullptr;
          if(*this)//per fare questo devo aver implementato l'overload dell'operatore bool e quindi aver deciso i casi per cui restituisce true o false
          {
            temp = new BaseStruct2(/*passo quello che serve*/);
          }
          return temp;
        }
        Code getType() const override
        {
          return Type2;
        };
    };

    creo una classe Factory fatta in modo che crei il corretto oggetto derivato da Base

    codice:
    class Factory
    {
      public:
        static Base* make(BaseStruct* data)
        {
          Base* temp = nullptr;
          if(data)
          {
            Code code = data->getType();
            switch (code)
            {
              case Type1:
                temp = makeDerived1(data);
                break;
              case Type2:
                temp = makeDerived2(data);
                break;
              default:
                break;
            };
          }
          return temp;
        };
      private:
        static Base* makeDerived1(BaseStruct* data);
        static Base* makeDerived2(BaseStruct* data);
    };

    creo poi una classe che funga da interfaccia per non dovermi preoccupare di che classe concreta derivata da base stia utilizzando.

    codice:
    class Interfaccia
    {
      public:
        //...metodi vari
        Interfaccia(BaseStruct* data):mBase(Factory::make(data)){};//se data e' nullptr anche mBase contiene nullptr
        void set(BaseStruct* data)
        {
          if(mBase)//prima verifico che effettivamente mBase non sia nullptr, perche' potrei averlo creato cosi' per come e' implementato Factory::make
          {
            if(data)//verifico che data non sia un nullptr
            {
              if(data->getType() == mBase->getType())
              {
                mBase->set(data);
              }
            }
            else
            {
              //qui ho tre opzioni:
              //1 - faccio niente
              //2 - mBase.reset(Factory::make(data)); anche se mi sembra il comportamento che crea piu' difficolta di manutenzione, se voglio cambiare
              //    il tipo di mBase, allora e' forse meglio creare un metodo convert(BaseStruct* data) che svolge questo compito
              //3 - eccezione
            }
          }
          else
          {
            //tre opzioni
            //1 - faccio niente
            //2 - eccezione
            //3 - mBase.reset(Factory::make(data));
          }
        };
        BaseStruct* get() const
        {
          BaseStruct* temp = nullptr;
          if(mBase)
          {
            temp = mBase->get();
          }
          return temp;
        };
        Code getType()
        {
          if(mBase)
          {
            return mBase->getType();
          }
          else
          {
            //due opzioni
            //1 - nell'Enum Code aggiungo NoType ad esempio e restituisco questo valore
            //2 - eccezione
          }
        };
      private:
        std::unique_ptr<Base> mBase;
    };


    Cosi' su due piedi direi che probabilmente conviene mettere le eccezioni nella gerachia di classi e gestirle nei singoli metodi della classe interfaccia

    quindi potrebbe diventare ad esempio

    codice:
    class Interfaccia
    {
      public:
        //...metodi vari
        Interfaccia(BaseStruct* data):mBase(Factory::make(data)){};//se data e' nullptr anche mBase contiene nullptr
        void set(BaseStruct* data)
        {
          if(mBase)//prima verifico che effettivamente mBase non sia nullptr, perche' potrei averlo creato cosi' per come e' implementato Factory::make
          {
            if(data)//verifico che data non sia un nullptr
            {
              if(data->getType() == mBase->getType())
              {
                mBase->set(data);
              }
            }
            else
            {
              throw std::MyException("Eccezione");
            }
          }
          else
          {
            throw std::MyException("Altra eccezione");
          }
          catch (std::exception& e)
          {
            std::cerr << "exception: " << e.what() << '\n';
          }
        };
        BaseStruct* get() const
        {
          BaseStruct* temp = nullptr;
          if(mBase)
          {
            temp = mBase->get();
          }
          return temp;
        };
        Code getType()
        {
          if(mBase)
          {
            return mBase->getType();
          }
          else
          {
            //due opzioni
            //1 - nell'Enum Code aggiungo NoType ad esempio e restituisco questo valore
            //2 - eccezione
          }
        };
      private:
        std::unique_ptr<Base> mBase;
    };
    Qualche consiglio? Grazie

  2. #2
    Utente di HTML.it L'avatar di shodan
    Registrato dal
    Jun 2001
    Messaggi
    2,381
    Ma i dubbi riguardano se usare le eccezioni?
    Per il resto osservo che la classe factory può essere semplificata.
    This code and information is provided "as is" without warranty of any kind, either expressed
    or implied, including but not limited to the implied warranties of merchantability and/or
    fitness for a particular purpose.

  3. #3
    Utente di HTML.it L'avatar di ing82
    Registrato dal
    Sep 2014
    Messaggi
    177
    Quote Originariamente inviata da shodan Visualizza il messaggio
    Ma i dubbi riguardano se usare le eccezioni?
    In effetti non e' molto chiara la richiesta.

    Il primo punto e' il metodo set della classe Interfaccia: dato che passo un puntatore che per i piu' disparati motivi potrebbe non essere quello atteso dal dato membro mBase, e dato che il senso del metodo e' quello di settare i dati che gli passo, mi sembra fuorviante o comunque poco consistente permettere che venga cambiato il tipo di mBase secondo il tipo di Struct che gli passo. Se ho creato un oggetto di un certo tipo e gli passo una struttura dati di un tipo diverso, col solo metodo set implementato non riesco a capire se cambio il tipo di mBase per errore mio o perche' lo voglio veramente, ma a quel punto vale la pena implementare forse un metodo dedicato che faccia questo.
    Altra cosa se mBase non e' ancora stato inizializzato o per qualsiasi motivo e' diventato nullptr: anche in questo caso, nella logica per cui viene implementato il metodo set, cioe' settare i dati membro di un oggetto, se mBase e' nullptr lo inizializzo col metodo set, o meglio sollevare un'eccezione?

    Per il metodo getType: se mBase e' nullptr, cosa ha piu' senso, eccezione o restituisco comunque un valore che indica che mBase in realta' e' nullptr?

    Stessa cosa per get: se mBase e' nullptr, che si fa di solito, restituisco un nullptr, indice della situazione anomala, o eccezione?

    Mi rendo conto che sono scelte personali, che una volta fatte e messe nei commenti al codice, sia una che l'altra strada possono essere valide, pero' vorrei sapere cosa si tende a fare in genere in queste situazioni.

    Per i metodi delle classi derivate, in base a quanto scelto per la classe interfaccia, si opera di conseguenza.

    Quote Originariamente inviata da shodan Visualizza il messaggio
    Per il resto osservo che la classe factory pu� essere semplificata.
    Questa mi incuriosisce, chiedo troppo se chiedo "come la semplificheresti?"

    Grazie.

  4. #4
    Utente di HTML.it L'avatar di shodan
    Registrato dal
    Jun 2001
    Messaggi
    2,381
    Il primo punto e' il metodo set della classe Interfaccia: dato che passo un puntatore che per i piu' disparati motivi potrebbe non essere quello atteso dal dato membro mBase, e dato che il senso del metodo e' quello di settare i dati che gli passo, mi sembra fuorviante o comunque poco consistente permettere che venga cambiato il tipo di mBase secondo il tipo di Struct che gli passo. Se ho creato un oggetto di un certo tipo e gli passo una struttura dati di un tipo diverso, col solo metodo set implementato non riesco a capire se cambio il tipo di mBase per errore mio o perche' lo voglio veramente
    Il pattern strategy funziona così. Costruisci un oggetto in un modo, poi puoi cambiarlo a piacimento tramite un parametro e una funzione. Se i dati che passi in Interfaccia::set() si riferiscono a una classe derivata e non a quella attualmente in essere dentro mBase, cambi l'oggetto dentro mBase. Se invece i dati si riferiscono all'oggetto dentro mBase aggiorni l'oggetto.
    Non è ne fuorviante ne poco consistente.

    Altra cosa se mBase non e' ancora stato inizializzato o per qualsiasi motivo e' diventato nullptr: anche in questo caso, nella logica per cui viene implementato il metodo set, cioe' settare i dati membro di un oggetto, se mBase e' nullptr lo inizializzo col metodo set, o meglio sollevare un'eccezione?
    se mBase è nullptr non puoi proprio usarlo per accedere ai metodi, pertanto il problema non si pone nemmeno.
    Eccezione? Non serve. Se mBase è nullptr lo crei e lo imposti con i dati che ti servono. Altrimenti hai questo:
    codice:
    Interfaccia ai(nullptr);
    ai.set(puntatore valido) // bang!!!
    Non mi pare abbia senso.

    Per il metodo getType: se mBase e' nullptr, cosa ha piu' senso, eccezione o restituisco comunque un valore che indica che mBase in realta' e' nullptr?
    Stessa cosa per get: se mBase e' nullptr, che si fa di solito, restituisco un nullptr, indice della situazione anomala, o eccezione?

    Mi rendo conto che sono scelte personali, che una volta fatte e messe nei commenti al codice, sia una che l'altra strada possono essere valide, pero' vorrei sapere cosa si tende a fare in genere in queste situazioni.
    Bella domanda! C'è chi non userebbe le eccezioni nemmeno sotto tortura e chi ci condirebbe pure l'insalata.
    Posso dirti come mi regolo io, ossia usare le eccezioni se è violato un prerequisito oppure se non so che fare.
    Stroustroup una volta ha detto che sono solo due le eccezioni problematiche: una bad_cast in caso di dynamic_cast fallito e la bad_alloc se non c'è più memoria. Il resto degli errori sono controllabili, se qualcuno li controlla.
    C'è questo bell'articolo sul tema
    https://www.codeproject.com/articles...-pros-and-cons

    Ad esempio il fatto che mBase non possa essere nullptr è un prerequisito?
    Se si allora ogni volta che mBase è nullptr lancio l'eccezione, altrimenti gestisco la cosa tramite qualche if.
    Il parametro data != nullptr è un prerequisito? Come sopra.

    Questa mi incuriosisce, chiedo troppo se chiedo "come la semplificheresti?"
    Ho semplicemente pensato che all'interno dei case dello switch potresti scrivere direttamente il codice delle makeDerivedX dal momento che gli passi il parametro data, ma probabilmente è più pulito così com'è.
    This code and information is provided "as is" without warranty of any kind, either expressed
    or implied, including but not limited to the implied warranties of merchantability and/or
    fitness for a particular purpose.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.