2014-12-15 3 views
5

ich einige Daten in class packen oft Fehler mit öffentlichen/globalen Zugriff zu verhindern und einige gemeinsame Methoden darauf bieten, zum Beispiel:„onEachSubelement (...)“ Methode für C++

class GameArea{ 
    std::vector<Enemy*> enemies; 
    std::wstring name; 
public: 
    void makeAllEnemiesScared(); 
    Enemy * getEnemy(unsigned index); 
}; 

GameArea ist nur ein vereinfachtes Beispiel hier. Es wäre eine Art von benutzerdefinierten Container/Menager mit einigen spezialisierten Methoden (aber es ist nicht nur ein Container).

Die ideale Situation ist, wenn ich weiß, welche Operationen an jedem Enemy auf einmal ausgeführt wird, und sie treten an mehreren Stellen, damit ich sie in GameArea direkt erklären können (wie makeAllEnemiesScared()).

Für andere Fälle, die ich mit gehen kann:

for(unsigned i=0; i<gameArea->getEnemiesCount(); i++){ 
    Enemy * enemy = gameArea->getEnemy(i); 
    ... 
} 

Aber es leidet unter einigen Nachteilen:

  • Ich kann nicht C++ 11 sauber & schön for(auto &Enemy : enemies) Schleife verwenden,
  • Es ist nicht effizient (so viele Anrufe zu getEnemy(index)),
  • Es ist nicht ein Zweck für getEnemy(index) zu iterieren werfen alle Elemente - es ist nützlich, wenn wir einzelne oder wenige von ihnen auswählen möchten, hat es auch für index < enemies.size() innen überprüfen - es ist schrecklich, es auf jedem Element in Schleife zu überprüfen.

HINWEIS: denke ich Fälle über, wenn ich etwas ganz Besonderes tun (nicht wert in GameArea getrennt Methode zu schaffen, aber auf jedes Element von GameArea::enemies).

Ich dachte über einige GameArea::onEachEnemy(... function ...) Methode, die eine function (oder vielleicht besser ein Lambda?) Als Parameter verwendet. Ist das eine gute Lösung?

Oder vielleicht anderen Ansatz sollte hier verwendet werden? Wie die std::vector von GameArea zurückgeben - die für mich ein bisschen "hässlich" aussieht. Ich möchte nicht, dass der Benutzer denkt, dass er Artikel direkt zu dieser vector hinzufügen oder entfernen kann.

+1

Ihr "onEachEnemy" Vorschlag klingt gut für mich. – molbdnilo

+1

Ich gehe davon aus, dass alle "getEnemy" ist es, 'Feinde [i]' zurückzukehren? Dann können Sie die Funktion inline und der Compiler wird den Aufruf wahrscheinlich optimieren. Sie können den wiederholten Aufruf 'getEnemiesCount' auch vermeiden, indem Sie ihn vor der Schleife aufrufen und das Ergebnis speichern. Sie können auch eine Iterator-Schnittstelle bereitstellen oder, wie Sie sich fragen, eine "für jede" -Funktion erstellen. Aber vor allem sollten Sie Ihr Programm wahrscheinlich profilieren, um zu sehen, ob das wirklich der Flaschenhals ist, den Sie denken, dass es ist. –

+0

Oh, und denken Sie daran, dass der Vektor 'operator []' - Funktion keine Grenzen prüft, es nimmt an, dass der Benutzer des Vektors darauf achten wird, nicht außerhalb der Grenzen zu gehen. Wenn Sie sich Sorgen über Ineffizienz machen, sollten Sie wahrscheinlich das Gleiche tun, d. H. Die Überprüfung der Grenzen von "getEnemy" abbrechen. Aber wie ich in meinem vorherigen Kommentar gesagt habe, * Profil * zuerst, und stellen Sie sicher, dass dies wirklich so ineffizient ist, wie Sie denken, dass es ist. –

Antwort

3

Eine gute Lösung sein könnte, die nicht Interna Ihrer Klassen aussetzt wie Sie ist, vorgeschlagen, eine Funktion eine Aktion für jedes Objekt aufrufen:

class GameArea { 
... 
    template<typename Func> void ForEachEnemy(Func f) 
    { 
    std::for_each(enemies.begin(), enemies.end(), f); 
    } 
... 

Dann können Sie alles passieren wollen Sie als ForEachEnemy Argument - globale Funktion, boost::bind Ergebnis usw.

+0

Ich ging schließlich mit dieser Option, aber für alle Leser - überprüfen Sie alle Antworten, weil die vorgeschlagenen Lösungen (Exposition Vektor selbst, Exposition Anfang/Ende Iterator, mit 'hat eine' anstelle von 'ist eine' Beziehung) sind gut und passt vielleicht besser zu deiner eigenen Situation :) – PolGraphic

4

Wenn Sie den Vektor selbst oder zumindest Iteratoren aussetzen begin und end, können Sie std::for_each

Dann würden Sie dies als

std::for_each(std::begin(enemies), std::end(enemies), foo); 

verwenden, wo foo die Funktion, die Sie anrufen möchten, ist auf jede Enemy.

Wieder beachten Sie müssen den Vektor selbst aus, da Sie Methoden in GameArea machen könnte Iteratoren zu bekommen, damit der Anruf wie

std::for_each(gameArea->GetEnemyBegin(), gameArea->GetEnemyEnd(), foo); 
+0

Das ist eine ziemlich schlaue Alternative für mein 'GameArea :: onEachEnemy (... Funktion ...)', danke :) Ich werde ein bisschen auf andere Antworten warten, um eine Chance zu bekommen, weitere Ideen zu bekommen, aber ich habe es schon hat es gewählt. – PolGraphic

+1

Beachten Sie, dass, wenn Sie [Iteratoren in der richtigen Weise verfügbar machen] (http://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for- Schleifen) wird es auch mit C-11-basierten for-Schleifen funktionieren. –

1

Sie könnten das Iterator Muster betrachten. Auf diese Weise können Sie die Kapselung beibehalten, haben aber dennoch etwas, das bequem zu verwenden ist.

Sie können entweder Standard-Container-Iteratoren verfügbar machen oder, wenn Sie eine vollständige Kapselung wünschen, schreiben Sie den Iterator selbst. Wenn Sie den Iterator richtig schreiben können Sie sogar C++ verwenden 11 bereichsbasierte for-Schleifen:

#include <vector> 
#include <string> 
#include <iostream> 

struct Enemy{ std::string name; }; 

class EnemyIterator { 
    friend class GameArea; 
private: 
    std::vector<Enemy>::iterator iterator; 
    EnemyIterator(std::vector<Enemy>::iterator it) : iterator(it){} 
public: 
    EnemyIterator& operator++() { ++iterator; return *this; } 
    Enemy& operator*() { return *iterator; } 
    friend bool operator!=(EnemyIterator lhs, EnemyIterator rhs) { 
    return lhs.iterator != rhs.iterator; 
    } 
}; 

class GameArea{ 
    std::vector<Enemy> enemies; 
public: 
    EnemyIterator begin() { return EnemyIterator(enemies.begin()); } 
    EnemyIterator end() { return EnemyIterator(enemies.end()); } 
    void addEnemy(std::string name) {enemies.push_back(Enemy{name}); } 
}; 

int main() { 
    GameArea area; 
    area.addEnemy("Kobold"); 
    area.addEnemy("Wraith"); 
    area.addEnemy("Ork"); 
    for (auto& enemy : area) 
     std::cout << enemy.name << "\n"; 
} 

Um für C++ 11 gefächerte basierend for-Schleifen mit GameArea Sie arbeiten müssen, eine von dem tun:

  • definieren beginnen und das Endglied Funktionen
  • define beginnen und enden nicht-Member-Funktionen im gleichen Namensraum
  • spezialisiert std :: beginnen und std :: Ende