2010-03-22 22 views
7

Ich möchte eine Reihe von Objekten von Klassen verwalten, die von einer gemeinsamen Interface-Klasse in einem gemeinsamen Container abgeleitet sind.Was wäre der sicherste Weg, Objekte von Klassen zu speichern, die von einer gemeinsamen Schnittstelle in einem gemeinsamen Container abgeleitet sind?

Um das Problem zu veranschaulichen, lasst uns sagen, ich baue ein Spiel, das verschiedene Akteure enthalten wird. Lassen Sie uns die Schnittstelle IActor aufrufen und davon Enemy und Civilian ableiten.

Nun ist die Idee, mein Spiel Hauptschleife der Lage sein, dies zu tun:

// somewhere during init 
std::vector<IActor> ActorList; 
Enemy EvilGuy; 
Civilian CoolGuy; 
ActorList.push_back(EvilGuy); 
ActorList.push_back(CoolGuy); 

und

// main loop 
while(!done) { 
    BOOST_FOREACH(IActor CurrentActor, ActorList) { 
     CurrentActor.Update(); 
     CurrentActor.Draw(); 
    } 
} 

... oder etwas in diese Richtung. Dieses Beispiel wird offensichtlich nicht funktionieren, aber das ist ziemlich genau der Grund, warum ich hier frage.

Ich würde gerne wissen: Was wäre die beste, sicherste, höchste Art und Weise, um diese Objekte in einem gemeinsamen heterogenen Container zu verwalten? Ich kenne eine Vielzahl von Ansätzen (Boost :: Any, void *, Handler-Klasse mit boost :: shared_ptr, Boost.Pointer Container, dynamic_cast), aber ich kann mich nicht entscheiden, welcher Weg so gehen würde.

Auch möchte ich betonen, dass ich so weit wie möglich von der manuellen Speicherverwaltung oder verschachtelten Zeigern entfernt bleiben möchte.

Hilfe sehr geschätzt :).

Antwort

3

Wie Sie vermutet haben, müssen Sie die Objekte als Zeiger speichern.
Ich bevorzuge die Verwendung der Boost-Pointer-Container (anstelle eines normalen Containers von Smartpointern).

Der Grund dafür ist der Boost ptr Container Zugriff auf die Objekte als ob sie Objekte (Rückgabe von Referenzen) und nicht Zeiger. Dies erleichtert die Verwendung von Standardfunktionen und -algorithmen auf den Containern.

Der Nachteil von Smartpointern ist, dass Sie die Eigentumsrechte teilen.
Das ist nicht was du wirklich willst. Sie möchten, dass der Besitz an einem einzigen Ort ist (in diesem Fall der Container).

boost::ptr_vector<IActor> ActorList; 
ActorList.push_back(new Enemy()); 
ActorList.push_back(new Civilian()); 

und

std::for_each(ActorList.begin(), 
       ActorList.end(), 
       std::mem_fun_ref(&IActor::updateDraw)); 
+0

gefiel die Art, wie Sie for_each verwendet –

+0

Können Sie erklären, Ihre for_each und wie Sie es mit BOOST_FOREACH verwenden würden? – Svenstaro

+0

std :: for_each (I1, I2, Aktion).Wendet die Aktion auf alle Werte an (in diesem Fall ruft die Methode updateDraw auf) im Bereich zwischen I1 und I2 (ohne I2). Wo I1, I2 sind Iteratoren. Sehen Sie: http://www.sgi.com/tech/stl/for_each.html –

4

Meine sofortige Reaktion ist, dass Sie intelligente Zeiger im Container speichern sollten, und stellen Sie sicher, dass die Basisklasse genügend (reine) virtuelle Methoden definiert, die Sie nie wieder in die abgeleitete Klasse benötigen.

10

Um das Problem zu lösen, das Sie erwähnt haben, obwohl Sie in die richtige Richtung gehen, aber Sie tun es falsch. Dies ist, was Sie brauchen würden

  • Definieren Sie eine Basisklasse zu tun mit virtuellen Funktionen (die Sie bereits tun), die von abgeleiteten Klassen überschrieben werden würde Enemy und Civilian in Ihrem Fall.
  • Sie müssen einen geeigneten Container auswählen, in dem Ihr Objekt gespeichert wird. Sie haben eine std::vector<IActor> genommen, die keine gute Wahl ist, weil
    • Zuerst wenn Sie Objekte zum Vektor hinzufügen, führt es zum Objekt Slicing. Dies bedeutet, dass nur der IActor Teil von Enemy oder Civilian anstelle des gesamten Objekts gespeichert wird.
    • Zweitens müssen Sie Funktionen abhängig vom Typ des Objekts aufrufen (virtual functions), was nur passieren kann, wenn Sie Zeiger verwenden.

Sowohl der Grund oben weisen darauf hin, dass Sie einen Behälter verwenden, müssen die Zeiger enthalten kann, so etwas wie std::vector<IActor*>. Aber eine bessere Wahl wäre, container of smart pointers zu verwenden, die Sie von Gedächtnismanagementköpfen ersparen wird.Sie können auf Ihren Bedarf je einem der Smart-Pointer verwenden (aber nicht auto_ptr)

Dies ist, was Ihr Code wie

// 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())); 

und

// main loop 
while(!done) 
{ 
    BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList) 
    { 
     CurrentActor->Update(); 
     CurrentActor->Draw(); 
    } 
} 

die so ziemlich ähnlich aussehen würde Ihr Originalcode mit Ausnahme der Smartpointer Teil

+3

speziell mögen Sie einen Smart-Pointer mit Kopie Semantik –

+0

yup ich mit diesen –

+1

zustimmen mag ich Ihren Ansatz, wie es für einen Zeiger basierten Ansatz sehr sauber aussieht. Wie andere jedoch bereits erwähnt haben, ist das, was ich machen möchte, wahrscheinlich besser mit Boost Pointer Containern erledigt, da sie in der Tat so aussehen, als wären sie genau das, was ich erreichen möchte. Ich werde Ihren Ansatz versuchen, sollte das scheitern. Oder sehen Sie einen Grund * nicht * Boost Pointer Container zu verwenden? – Svenstaro

3

Wenn der Container ausschließlich die Elemente enthalten soll, verwenden Sie einen Boost-Pointer-Container: Sie sind entworfen für diesen Job. Verwenden Sie andernfalls einen Container mit der Nummer shared_ptr<IActor> (und verwenden Sie sie natürlich ordnungsgemäß, was bedeutet, dass jeder, der das Eigentumsrecht teilen muss, shared_ptr verwendet).

Stellen Sie in beiden Fällen sicher, dass der Destruktor IActor virtuell ist.

void* erfordert Sie manuelle Speicherverwaltung, so dass es aus ist. Boost.Any ist Overkill, wenn die Typen durch Vererbung verwandt sind - Standard-Polymorphie macht den Job.

Ob Sie dynamic_cast benötigen oder nicht, ist ein orthogonales Problem - wenn die Benutzer des Containers nur die IActor-Schnittstelle benötigen und entweder (a) alle Funktionen der Schnittstelle virtuell machen oder andernfalls (b) die non verwenden -virtuelle Schnittstelle Idiom, dann brauchen Sie nicht dynamic_cast. Wenn die Benutzer des Containers wissen, dass einige der IActor-Objekte "wirklich" Zivilisten sind und Dinge nutzen wollen, die sich in der Civilian-Oberfläche befinden, aber nicht in IActor, dann brauchen Sie Umwandlungen (oder ein Redesign).