Quale sarebbe il modo più sicuro per memorizzare gli oggetti delle classi derivate da un'interfaccia comune in un contenitore comune?
-
21-09-2019 - |
Domanda
Mi piacerebbe gestire un gruppo di oggetti di classi derivate da una classe di interfaccia condivisa in un contenitore comune.
Per illustrare il problema, diciamo che mi sto costruendo un gioco che conterrà diversi attori. Chiamiamo il IActor
dell'interfaccia e derivano Enemy
e Civilian
da esso.
Ora, l'idea è quella di avere il mio gioco ciclo principale essere in grado di fare questo:
// somewhere during init
std::vector<IActor> ActorList;
Enemy EvilGuy;
Civilian CoolGuy;
ActorList.push_back(EvilGuy);
ActorList.push_back(CoolGuy);
e
// main loop
while(!done) {
BOOST_FOREACH(IActor CurrentActor, ActorList) {
CurrentActor.Update();
CurrentActor.Draw();
}
}
... o qualcosa del genere. In questo esempio, ovviamente, non funzionerà, ma che è più o meno la ragione per cui mi sto chiedendo qui.
Mi piacerebbe sapere: Quale sarebbe il migliore, più sicuro, modo più alto livello per gestire tali oggetti in un contenitore eterogeneo comune? So di una varietà di approcci (boost :: Qualsiasi, * vuoto, classe del gestore con boost :: shared_ptr, Boost.Pointer Container, dynamic_cast), ma non riesco a decidere quale sarebbe il modo di andare qui.
Inoltre vorrei sottolineare che io voglio stare lontano il più lontano possibile dalla gestione della memoria manuale o puntatori nidificate.
Guida molto apprezzato:).
Soluzione
Come avete indovinato è necessario memorizzare gli oggetti come puntatori.
Io preferisco usare i contenitori puntatore spinta (piuttosto che un normale contenitore di puntatori intelligenti).
La ragione di questo è l'accesso spinta contenitore di PTR gli oggetti come se fossero oggetti (che tornano i riferimenti), piuttosto che puntatori. Questo rende più facile da usare funtori standard e algoritmi sui contenitori.
Lo svantaggio di puntatori intelligenti è che si sta condividendo la proprietà.
Questo non è ciò che si vuole veramente. Si vuole la proprietà di essere in un unico luogo (in questo caso il contenitore).
boost::ptr_vector<IActor> ActorList;
ActorList.push_back(new Enemy());
ActorList.push_back(new Civilian());
e
std::for_each(ActorList.begin(),
ActorList.end(),
std::mem_fun_ref(&IActor::updateDraw));
Altri suggerimenti
Per risolvere il problema, che si è detto, anche se si sta andando nella giusta direzione, ma si sta facendo nel modo sbagliato. Questo è quello che si avrebbe bisogno di fare
- Definire una classe di base (che si sta già facendo) con funzioni virtuali che sarebbe essere invocati classi derivate
Enemy
eCivilian
nel tuo caso. - È necessario scegliere un contenitore adeguato con memorizzerà il vostro oggetto. Avete preso un
std::vector<IActor>
che non è una buona scelta, perché- In primo luogo quando si aggiungono oggetti al vettore che sta portando ad opporsi affettare. Ciò significa che solo la parte
IActor
diEnemy
oCivilian
viene memorizzato anziché l'intero oggetto. - In secondo luogo è necessario chiamare funzioni a seconda del tipo di oggetto (
virtual functions
), che può avvenire solo se si utilizzano i puntatori.
- In primo luogo quando si aggiungono oggetti al vettore che sta portando ad opporsi affettare. Ciò significa che solo la parte
Sia del motivo precedente punto al fatto che è necessario utilizzare un contenitore che può contenere puntatori, qualcosa come std::vector<IActor*>
. Ma una scelta migliore sarebbe quella di utilizzare container of smart pointers
che vi farà risparmiare dai problemi di gestione della memoria. È possibile utilizzare uno qualsiasi dei puntatori intelligenti a seconda delle vostre necessità (ma non auto_ptr
)
Questo è ciò che il codice sarà simile
// somewhere during init
std::vector<some_smart_ptr<IActor> > ActorList;
ActorList.push_back(some_smart_ptr(new Enemy()));
ActorList.push_back(some_smart_ptr(new Civilian()));
e
// main loop
while(!done)
{
BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList)
{
CurrentActor->Update();
CurrentActor->Draw();
}
}
Il che è più o meno simile al vostro codice originale tranne che per i puntatori intelligenti parte
La mia reazione immediata è che si dovrebbe memorizzare puntatori intelligenti nel contenitore, e assicurarsi che la classe base definisce abbastanza (puri) metodi virtuali che non hai mai bisogno di dynamic_cast
di nuovo alla classe derivata.
Se si desidera che il contenitore di possedere esclusivamente gli elementi in esso, utilizzare un contenitore puntatore Boost: sono progettati per quel lavoro. In caso contrario, utilizzare un contenitore di shared_ptr<IActor>
(e naturalmente usarli correttamente, il che significa che tutti coloro che hanno bisogno di condividere la proprietà utilizza shared_ptr
).
In entrambi i casi, assicurarsi che il distruttore di IActor
è virtuale.
void*
richiede di fare gestione manuale della memoria, in modo che è fuori. Boost.Any è eccessivo quando i tipi sono legati per eredità -. Polimorfismo di serie fa il lavoro
Se avete bisogno di dynamic_cast
o non è un problema ortogonali - se gli utenti del contenitore devono solo l'interfaccia IActor, e si sia (a) effettuare tutte le funzioni della interfaccia virtuale, oppure (b) utilizzare la non interfaccia linguaggio virtuale, quindi non è necessario dynamic_cast. Se gli utenti del contenitore sanno che alcuni degli oggetti IActor sono "realmente" i civili, e vogliono fare uso di cose che sono nell'interfaccia civile, ma non IActor, allora si avrà bisogno calchi (o una riprogettazione).