"onEachSubelement (...)" method for C ++

I often collect some data in a class to prevent public / global access errors and provide some common methods on it, for example:

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

GameArea is just a simplified example here. This will be a kind of custom container / menager with some specialized methods (but it's not just a container).

The ideal situation is when I know what operations will be performed on each Enemy once, and they occur in several places, so I can declare them directly in GameArea (for example, makeAllEnemiesScared() ).

Other times I can go with:

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

But it has some disadvantages :

  • I can't use C ++ 11 clean & nice for(auto &Enemy : enemies) loop,
  • This is inefficient (so many calls to getEnemy(index) )
  • This is not the goal for getEnemy(index) to iterate over all elements - this is useful in the case when we want to select one or more of them, it also has a check on index < enemies.size() inside - it is terrible to check it on every element in cycle.

NOTE: I think of cases when I do something very special ( you should not create a divided method in GameArea , but for each element > of GameArea::enemies ).

I was thinking of some kind of GameArea::onEachEnemy(... function ...) method that takes function as a parameter (or maybe a better lambda?). This is a good decision?

Or maybe a different approach should be used? Like returning std::vector from GameArea - which looks a bit ugly to me. I do not want the user to think that he can actually add or remove elements to / from this vector directly.

+5
source share
3 answers

A good solution that does not expose the interiors of your classes is, as you said, a function that calls an action for each object:

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

Then you can pass whatever you want as the ForEachEnemy argument - a global function, the result of boost::bind , etc.

+3
source

If you expose the vector itself, or at least begin and end iterators, you can use std::for_each

Then you will use this as

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

where foo is the function you want to call on each Enemy .

Please note again that you do not need to set the vector itself, you could make methods in GameArea to get iterators so that the call can look like

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

You might want to consider the Iterator template. This way you can keep encapsulation, but still have something that is convenient to use.

You can either set standard container iterators, or if you want to complete encapsulation, write Iterator yourself. If you write Iterator correctly, you can even use C ++ 11 based loops for loops:

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

In order for C ++ 11, a range-based loop for working with GameArea , you need to do one of the following:

  • define the start and end functions of an element
  • define begin and end non-member functions in the same namespace
  • specialize std :: begin and std :: end
+1
source

Source: https://habr.com/ru/post/1209104/


All Articles