Often times, I need to do bulk operations on a set of class data. Consider these classes:
#include <vector>
class Component {
public:
bool isFixed;
int a, b, c;
Component():
isFixed(false),
a(0), b(0), c(0)
{}
};
class System {
public:
std::vector<Component> components;
System(int numComponents):
components(numComponents)
{}
};
class Universe {
public:
std::vector<System> systems;
Universe(int numSystems, int numComponents):
systems(numSystems, System(numComponents))
{}
};
Now, doing a single operation to every Component
in a Universe
becomes a matter of looping through all the Component
s of all the System
s in a Universe
: a nested for
loop.
// Fixes a Component.
//
void fixComponent(Component& c) {c.isFixed = true;}
// Adds a number to the pieces of a Component.
//
void addToComponent(Component& cmp, double k)
{
cmp.a += k;
cmp.b += k;
cmp.c += k;
}
// Fixes all components in a Universe.
//
void fixAllComponents(Universe& universe)
{
for(std::size_t i = 0; i < universe.systems.size(); ++i) {
System& thisSystem = universe.systems.at(i);
for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
fixComponent(thisSystem.components.at(j));
}
}
}
// Adds a number to all components in a Universe.
//
void addToAllComponents(Universe& universe, double k)
{
for(std::size_t i = 0; i < universe.systems.size(); ++i) {
System& thisSystem = universe.systems.at(i);
for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
addToComponent(thisSystem.components.at(j), k);
}
}
}
Writing the iterative for
loops twice is OK, but I could easily have 20 different task to perform on this data, having to rewrite the double for
every time. Needless to say, this can be error-prone. It would be nice if I could somehow reuse the iterating code and just focus on the distinct individual tasks.
Question
Is there a standard way to "factor out" the for
loops when iterating over a set?
Attempt After some thinking, I decided to write a function that takes 2 parameters, the object containing the data to iterate over and a pointer to a function that performs the task.
void forEachComponent(Universe& u, void (*task)(Component&))
{
for(std::size_t i = 0; i < u.systems.size(); ++i) {
System& thisSystem = u.systems.at(i);
for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
task(thisSystem.components.at(j));
}
}
}
Now, if I want to fix a component, I can simply call forEachComponent()
and pass in the task to be performed.
Universe theUniverse(20, 30);
forEachComponent(theUniverse, fixComponent);
The obvious problem with this "solution" is that for every task that involves different parameters like addToComponent()
, I have to write another function that takes a pointer to a function with those parameters, which defeats the purpose of factoring out the for
loops.
You can reuse the logic for iterating over the components by using functors.
template <typename Functor >
void forEachComponent(Universe& universe, Functor functor)
{
for(std::size_t i = 0; i < universe.systems.size(); ++i) {
System& thisSystem = universe.systems.at(i);
for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
functor(thisSystem.components.at(j));
}
}
}
// Fixes all components in a Universe.
void fixAllComponents(Universe& universe)
{
forEachComponent(universe, [](Component& c) {fixComponent(c);});
}
// Adds a number to all components in a Universe.
void addToAllComponents(Universe& universe, double k)
{
forEachComponent(universe, [k](Component& c) {addToComponent(c, k);});
}
You can simplify forEachComponent
by using range-for
loops.
template <typename Functor >
void forEachComponent(Universe& universe, Functor functor)
{
for(System& system : universe.systems) {
for(Component& c : system.components) {
functor(c);
}
}
}
An OOP way to do this is define an abstract class:
class Task {
void execute(Component &) = 0;
}
Now you can define forEachComponent()
as
void forEachComponent(Universe& u, Task& task)
and call task.execute()
in the for loop.
Alternatively, you can define forEachComponet()
as a template:
template <class Task>
void forEachComponent(Universe& u, Task& task)
And now anything passed into this function must override operator()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With