Iterare su oggetti di una classe specifica in un contenitore di oggetti di base polimorfici

4

Supponiamo che ci sia una classe di oggetti base - lascia che sia chiamata Oggetto - e una lista < Oggetto > contenitore. Esistono molte classi figlio di Object - Child1 , Child2 ecc. Sono tutte archiviate nel contenitore.

Ho bisogno di un modo per iterare su sottoinsiemi di oggetti di qualche particolare classe. Dì, su tutti i Bambini1 o Bambini2 , mentre li stai raggruppando tutti in un unico contenitore in modo da poterli accedere in modo polimorfico.

Si noti che il contenitore viene ridimensionato dinamicamente di tanto in tanto.

Quali sono le possibili soluzioni di design per questo?

Attualmente ne ho creati due:

  • introduzione di un iteratore che viene eseguito sull'oggetto di controllo contenitore digita ogni volta
  • conservare contenitori aggiuntivi con tutti Child1 e tutti Child2 oggetti dal contenitore principale.

Onestamente, non mi piace in entrambi i casi: il primo, temo, sarà lento perché ci saranno davvero molti oggetti, il secondo è maldestro e richiede l'osservazione di ogni aggiunta di oggetti e cancellazione.

Lo sto scrivendo in C ++.

Aggiorna

Come ulteriore sviluppo della seconda soluzione, ho appena pensato di creare una sottoclasse di un elenco con metodi che restituirebbero sottoliste di particolari tipi di sottoclassi che potrebbero essere tenuti in cache e aggiornati su ogni aggiunta / eliminazione:

for(auto i: gameObjects.sublist<Child1>()) {
   ...
    
posta olegst 01.06.2017 - 09:07
fonte

4 risposte

8

Quando hai una collezione polimorfa come il tuo scenario, il problema che hai è che l'oggetto stesso sa cosa è ma nient'altro quindi qualsiasi cosa al di fuori del contenitore ha bisogno di qualche conoscenza in più per essere in grado per identificare cosa è cosa.

La soluzione diretta sarebbe migliorare il contenitore in modo da distinguere tra gli oggetti stessi. È possibile contenere una mappa di contenitori per ogni tipo di oggetto, che sono solo indicatori degli oggetti contenuti nell'elenco principale. Fai attenzione, usa i puntatori condivisi, fai attenzione a ciò che viene aggiunto ed eliminato. Se si limitano a fare riferimento agli oggetti, la memoria non dovrebbe espandersi. L'hai già detto. Non lo considererei goffo, ma pericoloso.

Detto questo, mi sembra che ci possa essere un problema di progettazione. Se un elenco di oggetti è in realtà un elenco di molti oggetti diversi, perché sono insieme?

Hai detto in un commento che si tratta di un gioco, quindi forse hai un elenco di oggetti di gioco generico, ma molti di questi oggetti di gioco sono nemici e ora stai cercando di afferrare tutti i nemici in uno script. Ti manca un passaggio: tutti i nemici sono oggetti di gioco, ma non tutti gli oggetti di gioco sono nemici, quindi vuoi un gestore intermediario per tutti i nemici. Forse ogni nemico si registra con il manager, automatizza il sistema il più possibile. Dopo tutto, cosa sta generando i nemici? Devi già avere qualcosa che manipoli l'elenco degli oggetti del gioco, espanderlo.

    
risposta data 01.06.2017 - 10:23
fonte
3

Le operazioni polimorfiche sono tradizionalmente gestite dal modello di visitatore . Come indica @Erdrik Ironrose, l'oggetto sa che è di tipo quindi lascia che faccia sia un lavoro. L'iterazione viene eseguita chiamando una funzione su ogni oggetto. Questa funzione chiama quindi una versione specifica del tipo di quella funzione. Alcuni oggetti potrebbero non supportare determinate funzioni e restituiranno semplicemente.

Ci sono molte varianti al modello di visitatore. Quello che stai cercando è un concetto di doppia spedizione. L'oggetto astratto viene chiamato con informazioni che indicano che dovrebbe chiamare qualche altra funzione con il suo tipo reale. Nel momento in cui l'oggetto chiama la funzione specifica del tipo, il tipo giusto sarà conosciuto e verrà chiamata la funzione corretta.

Ignorerò il problema che il tuo list<Object> dovrebbe essere list<shared_ptr<Object>> (o qualcosa di simile) poiché list<Object> troncerà ogni istanza all'oggetto della classe base.

    
risposta data 01.06.2017 - 17:27
fonte
3

Elaborando la risposta di Erdrick, vorrei fermamente sostenere che scarti il contenitore "tutto", e che tu abbia solo contenitori separati per tutti i Child1s, Child2s, ecc.

Se hai bisogno di ripetere occasionalmente su Genitori, combina tutti questi elementi (in un set o in un elenco appropriato). Questo aggiungerà del tempo, ma risparmierai un sacco di tempo durante l'iterazione sui tipi ChildX.

Solo se l'iterazione su Parent era comune e l'iterazione su ChildX era rara dovrei controllare il tipo di oggetto.

Nota, se la velocità è essenziale e la memoria non è un problema, puoi mantenere una raccolta separata di tutti i genitori, anche se la gestione della memoria sarà leggermente più complicata.

    
risposta data 01.06.2017 - 23:14
fonte
1

Prima di tutto, quando hai un oggetto polimorfico, non devi memorizzare un elenco di oggetti. Ciò comporterà slicing degli oggetti . È necessario memorizzare un elenco di puntatori, preferibilmente un elenco di puntatori intelligenti.

Venendo al problema di iterare sugli oggetti di un dato tipo derivato, puoi usare l'iterazione nel modo usato in un ciclo for , come ad esempio:

 for ( Object* ptr : container )
 {
    // If the pointer is of the right type, do something with it.
    // Otherwise, ignore it.
 }

o si itera nello stile di:

 iterator start = <some starting point>;
 iterator end = <some ending point>;
 for ( iterator iter = start; iter != end; ++iter )
 {
    Object* ptr = *iter;
    // If the pointer is of the right type, do something with it.
    // Otherwise, ignore it.
 }

Il primo caso può essere gestito in modo pulito utilizzando una funzione di stile for_each specifica dell'applicazione.

namespace MyApp
{
   template <typename ObjectType, typename Container, typename Functor>
   void for_each(Container const& container, Functor f)
   {
      for ( Object* ptr : container )
      {
         ObjectType* optr = dynamic_cast<ObjectType*>(ptr);
         if ( optr != nullptr )
         {
            f(optr);
         }
      }
   }
}

Il secondo caso può anche essere gestito in modo pulito utilizzando un'altra funzione di stile for_each specifica per l'applicazione.

namespace MyApp
{
   template <typename ObjectType, typename iterator, typename Functor>
   void for_each(iterator start, iterator end, Functor f)
   {
      for ( iterator iter = start; iter != end; ++iter )
      {
         Object* ptr = *iter;
         ObjectType* optr = dynamic_cast<ObjectType*>(ptr);
         if ( optr != nullptr )
         {
            f(optr);
         }
      }
   }
}

Puoi anche implementare il primo utilizzando il secondo.

namespace MyApp
{
   template <typename ObjectType, typename Container, typename Functor>
   void for_each(Container const& container, Functor f)
   {
      for_each<ObjectType>(container.begin(), container.end(), f);
   }
}

La tua preoccupazione - la prima, temo, sarà lenta perché ci saranno molti oggetti - è valida. Tuttavia, mi preoccuperei solo se diventerà una preoccupazione significativa nelle tipiche sessioni. La semplicità di questo approccio è più attraente per me di altri approcci.

    
risposta data 01.06.2017 - 22:17
fonte

Leggi altre domande sui tag