2014-12-15 15 views
5

często pakować jakieś dane do class aby uniknąć błędów z publicznym dostępem do Globalnej/i dostarczyć pewne wspólne sposoby na to, np:"onEachSubelement (...)" metoda C++

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

GameArea jest tylko uproszczony przykład tutaj. Byłby to rodzaj niestandardowego kontenera/menager z pewnymi specjalistycznymi metodami (ale to nie tylko kontener).

Idealną sytuacją jest, gdy wiem, jakie operacje będą wykonywane na każdym Enemy naraz i występują one w kilku miejscach, więc mogę zadeklarować je w GameArea bezpośrednio (jak makeAllEnemiesScared()).

W innych przypadkach mogę iść z:

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

Ale cierpi z niektórych wad:

  • Nie mogę korzystać z C++ 11 czystego & piękny for(auto &Enemy : enemies) pętli
  • To nie jest wydajne (tyle połączeń do getEnemy(index)),
  • To nie jest przeznaczenie dla getEnemy(index) do iterowania rzucania wszystkimi elementami - jest to użyteczne w przypadku, gdy chcemy wybrać pojedyncze lub kilka z nich, ma również sprawdzić dla index < enemies.size() wewnątrz - to jest okropne, aby sprawdzić to na każdym elemencie w pętli.

UWAGA: myślę o przypadkach, gdy robię coś wyjątkowego (nie warto stworzyć metodę oddzielony w GameArea, ale na każdym elemencie z GameArea::enemies).

Myślałem o pewnej metodzie GameArea::onEachEnemy(... function ...), która ma function (a może lepiej lambda?) Jako parametr. Czy to dobre rozwiązanie?

A może należy zastosować inne podejście? Podobnie jak zwrot std::vector z GameArea - który wygląda dla mnie trochę "brzydko". Nie chcę, aby użytkownik myślał, że może bezpośrednio dodawać lub usuwać elementy do/z tego numeru.

+1

Twoja sugestia "onEachEnemy" brzmi dla mnie dobrze. – molbdnilo

+1

Zakładam, że wszystkie 'getEnemy' ma na celu zwrócić' wrogów [i] '? Następnie możesz wbudować funkcję, a kompilator najprawdopodobniej zoptymalizuje wywołanie. Możesz także uniknąć powtarzającego się wywołania 'getEnemiesCount', wywołując je przed pętlą i zapisując wynik. Możesz także zapewnić interfejs iteratora lub, jak się zastanawiasz, utworzyć funkcję "dla każdego". Ale przede wszystkim powinieneś profilować swój program, aby sprawdzić, czy to naprawdę jest oszustwo, o którym myślisz. –

+0

Aha, i pamiętajcie, że wektor 'operator []' nie wykonuje żadnych sprawdzeń granicznych, zakłada, że ​​użytkownik wektora zadba o to, aby nie wyjść poza granice. Jeśli tak bardzo martwisz się nieefektywnością, prawdopodobnie powinieneś zrobić to samo, tj. Zrezygnować z sprawdzania granic z 'getEnemy'. Ale tak jak powiedziałem w poprzednim komentarzu * najpierw * profil i upewnij się, że to naprawdę jest tak nieefektywne, jak ci się wydaje. –

Odpowiedz

3

dobre rozwiązanie, które nie wystawiać wewnętrzne swoich klasach jest, jak sugeruje, funkcja nazywając akcję dla każdego obiektu:

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

Następnie można przekazać cokolwiek chcesz jako argumentu - ForEachEnemy funkcji globalnej boost::bind wyniku itp

+0

W końcu poszedłem z tą opcją, ale dla wszystkich czytelników - sprawdź WSZYSTKIE odpowiedzi, ponieważ proponowane rozwiązania (odsłaniając sam wektor, odsłaniając iterator początku/końca, używając 'ma a' zamiast 'to relacja') są dobre i może lepiej pasować do Twojej własnej sytuacji :) – PolGraphic

4

Jeśli wystawiać sam wektor, albo przynajmniej iteratorów begin i end, można użyć std::for_each

Następnie należy użyć tego jako

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

gdzie foo jest funkcja chcesz wezwać każdy Enemy.

Znowu pamiętać, nie trzeba narażać sam wektor, można zrobić metod w GameArea dostać iteratory więc połączenie może być jak

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

To całkiem sprytna alternatywa dla mojego 'GameArea :: onEachEnemy (... funkcja ...)', dziękuję :) Będę czekać, aż inne odpowiedzi dadzą szansę na zdobycie więcej pomysłów, ale ja już głosowało nad tym. – PolGraphic

+1

Należy pamiętać, że jeśli [eksponujecie iteratory we właściwy sposób] (http://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-z-rozpoczęciem-dla- pętle) będzie działać również z pętlami C++ 11 dla pętli. –

1

Być może warto wziąć pod uwagę wzór Iterator. W ten sposób możesz zachować hermetyzację, ale nadal masz coś wygodnego w użyciu.

Możesz wystawiać standardowe iteratory kontenerów lub jeśli chcesz kompletną enkapsulację, napisz Iterator samodzielnie. Jeśli piszesz iterator poprawnie można nawet użyć C++ 11 Zakres opartej na pętlach:

#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"; 
} 

Aby C++ 11 wahał pętle oparte na pracy z GameArea trzeba zrobić jeden:

  • określić początek i funkcje składowe end
  • definiują początek i koniec funkcji trzecich w tej samej przestrzeni nazw
  • specjalizują się std :: rozpocząć i std :: end