Hello all-
Random design question for you all. I'd be curious to see how others have solved this!
I've got objects that manage collections of things. For instance, an object that manages players, another that manages users, another that manages maps, another that manages physics objects, etc.
Each of these "Manager" objects has to be threadsafe. Multiple threads can be adding/removing objects all the time.
At the same time, other threads are trying to iterate. One thread may be iterating over players while another is adding a player, etc.
To support this, each Manager object exposes an iterator (or multiple iterators if the Manager contains different kinds of objects--for instance the Physics manager lets you iterate physics objects and collisions). You can ask the Manager for an iterator, and then use the iterator and the Manager to walk through all objects available. This lets clients iterate in a threadsafe manner.
So far, so good. But I have two additional constraints (take these as a given, I'd rather not go into these right now):
First, the header files are silent on implementation. That is, they expose an interface, including the iterators and iteration methods, but know nothing about internal data structures etc. (This is all in C++)
Second, clients must be able to allocate iterators on the stack. Iteration is common and should be lightweight, not requiring memory allocation.
Because the header doesn't expose any implementation details, I don't know the real size of an iterator.
For now, I do something like this:
// header file
class Manager {
public:
struct iterator_t {
char opaque[4 * sizeof(long)]; // best guess!
};
virtual void resetIterator(iterator_t& i) = 0;
virtual bool getNextObject(iterator_t& i, object_t& o) = 0;
// other methods...
};
And then the corresponding cpp file for that header is:
// cpp source file
#include (header)
class ManagerImpl : public Manager {
struct real_iterator_t {
collection_t::iterator iter;
int rvn;
void * otherData;
};
void resetIterator(iterator_t& i) {
real_iterator_t ri = (cast) i;
ri.iter = m_collection.begin();
ri.rvn = m_rvn;
ri.otherData = "foobar";
}
// and so on...
};
As you can see, I have to cast from the opaque (hopefully big enough!) public iterator to the actual private iterator in the C++ source file.
Is there a better way to do that?
And in general is there a better way to expose public iterators on Managers? I'm guessing this is a pattern that's been hit before.