2014-12-15 2 views
5

Spesso mi imballo alcuni dati in class per evitare errori con accesso pubblico/globale e per fornire alcuni metodi comuni su di esso, ad esempio:"onEachSubelement (...)" metodo per C++

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

GameArea è solo un esempio semplificato qui. Sarebbe una specie di container/menager personalizzato con alcuni metodi specializzati (ma non è solo un contenitore).

La situazione ideale è quando so che cosa saranno eseguite operazioni su ogni Enemy subito e si verificano in diversi luoghi modo che io possa dichiarare GameArea direttamente (come makeAllEnemiesScared()).

Per gli altri casi posso andare con:

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

Ma soffre di alcuni svantaggi:

  • non posso usare C++ 11 pulito & bello for(auto &Enemy : enemies) ciclo,
  • Non è efficiente (così tante chiamate allo getEnemy(index)),
  • Non è un scopo per getEnemy(index) per iterare gettare tutti gli elementi - è utile nel caso in cui vogliamo selezionare solo uno o alcuni di essi, ha anche il controllo per index < enemies.size() all'interno - è terribile controllarlo su ogni elemento in loop.

NOTA: penso a casi in cui faccio qualcosa di molto speciale (non vale la pena la creazione di metodo separato in GameArea, ma su ogni elemento diGameArea::enemies).

Ho pensato ad un metodo GameArea::onEachEnemy(... function ...) che prende come parametro un function (o forse meglio un lambda?). Questa è una buona soluzione?

O forse un approccio diverso dovrebbe essere usato qui? Come restituire il std::vector da GameArea - che mi sembra un po '"brutto". Non voglio che l'utente pensi che possa effettivamente aggiungere o rimuovere elementi da/a quello vector direttamente.

+1

Il tuo suggerimento "onEachEnemy" mi suona bene. – molbdnilo

+1

Suppongo che tutto il comando 'getEnemy' sia quello di restituire' nemici [i] '? Quindi è possibile allineare la funzione e il compilatore molto probabilmente ottimizzerà la chiamata. È inoltre possibile evitare la ripetuta chiamata 'getEnemiesCount' chiamandola prima del ciclo e salvando il risultato. Puoi anche fornire un'interfaccia iteratore o, come ti chiedi, creare una funzione "per ogni". Ma prima di tutto dovresti probabilmente profilare il tuo programma per vedere se questo è davvero il collo della bottiglia che pensi sia. –

+0

Oh, e ricorda che la funzione vector 'operator []' non esegue alcun controllo dei limiti, presuppone che l'utente del vettore abbia cura di non andare fuori limite. Se sei così preoccupato dell'inefficienza, probabilmente dovresti fare lo stesso, cioè eliminare i limiti controllando da 'getEnemy'. Ma come ho detto nel mio precedente commento, * profile * prima, e assicurarmi che questo sia davvero inefficiente come pensi tu sia. –

risposta

3

Una buona soluzione, che non espone interni delle classi è, come lei ha suggerito, una funzione di chiamata di un'azione per ogni oggetto:

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

Poi si può passare tutto quello che vuoi come ForEachEnemy argomento - funzione globale, boost::bind risultato ecc

+0

Alla fine ho optato per questa opzione, ma per tutti i lettori - controlla TUTTE le risposte perché le soluzioni proposte (esporre il vettore stesso, esporre inizio/fine iteratore, usando 'has a' invece di' is a' relation) sono buone e potrebbe adattarsi meglio alla tua situazione :) – PolGraphic

4

Se si espone il vettore stesso, o almeno iteratori begin e end, è possibile utilizzare std::for_each

allora si potrebbe utilizzare questo come

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

dove foo è la funzione che si desidera chiamare su ogni Enemy.

notare Anche in questo caso non c'è bisogno di esporre il vettore stesso, si potrebbe fare metodi in GameArea per ottenere iteratori quindi la chiamata potrebbe essere come

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

Questa è un'alternativa molto intelligente per il mio 'GameArea :: onEachEnemy (... function ...)', grazie :) Aspetterò un po 'per le altre risposte per dare una possibilità di ottenere più idee, ma ho già up-votato. – PolGraphic

+1

Nota che se [esponi gli iteratori nel modo giusto] (http://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for- loop) funzionerà anche con looping di C++ 11 per loop. –

1

Si potrebbe prendere in considerazione il modello Iterator. In questo modo puoi mantenere l'incapsulamento ma hai ancora qualcosa che è comodo da usare.

È possibile visualizzare gli iteratori di contenitore standard oppure, se si desidera completare l'incapsulamento, scrivere personalmente l'Iterator. Se si scrive correttamente l'Iterator si può anche utilizzare C++ 11 gamma-based per cicli:

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

Affinché C++ 11 variava-based per i cicli di lavorare con GameArea è necessario fare una delle seguenti:

  • definire inizio e funzioni membro finali
  • define iniziano e finiscono le funzioni di terzi nello stesso namespace
  • specializzati std :: iniziare e std :: end