Posted by
| Nick Gammon
Australia (23,133 posts) Bio
Forum Administrator |
Message
| The classes below use an STL map to automatically record pointer "ownership". The intention is that you could have (in a game for instance) pointers to things (like players) where other things might refer to them (eg. a player owns an object, a room contains a player, a player follows another player).
The problem is, though, that if you store the actual pointer then the pointer might be deleted (eg. player A follows player B, however midway through, player B disconnects, and his pointer is deleted. Now the pointer that player A has is invalid, however he doesn't know it).
To work around this, we store an identifier (id) instead of the actual pointer, and then store a mapping between identifiers and their pointers in a map.
The interesting thing about the code below is this is all automatic, provided you derive the class from the appropriate auto-map pointer class. See the example below for how you might do this. Then, doing a "new" automatically adds the newly created pointer to the appropriate map, and doing a "delete" removes it, without any extra lines of code being needed.
Finally, for type-safety, instead of straight integers the identifiers are a small class of their own, so the compiler will restrain you from accidentally using (say) a player ID when you meant to use a room ID.
objectmap.h file
/*
OK, what is all this about?
I am trying to solve the problem of things in a busy environment
(eg. a MUD) where you might have lots of pointers which are being
created and deleted (eg. players joining, leaving, objects being
created and destroyed), and you want to have things referring to each
other. eg. a player might be following another player, or an event might
refer to a player or object.
Rather than storing pointers to the thing-to-be-referred-to we will
get a unique ID and store that instead. Then, before attempting to use
the thing, we will look up its ID in the ID map. If found, the object
still exists and we can use the looked-up pointer. If the object has been
delete for any reason, then the lookup will fail, and we know the object
no longer exists.
The constructor is supplied the owner map (eg. a map of rooms), so we
know which map to remove ourselves from eventually, and can add ourselves
to it initially.
Later on, other things would use GetId to find the object's ID so they can
refer to it (by storing in a uint64 field).
Then, when they want to get the actual pointer they use FindObject (or similar)
to find the pointer to it. If NULL is returned, the object has gone away.
A note of caution - because deleting the object also deletes it from the owner
map you want to be careful with iterators, deleting an item from a map invalidates
the iterator. Thus the normal for_each would fail if something for_each calls
deletes the item it found (ie. the item deletes itself). The function provided below:
safe_for_each increments the iterator before calling the function so it should
work, even if the function deletes the object.
Example:
// owner map:
class tPlayer;
tPointerOwner<tPlayer> PlayerMap;
// example owned object - passes map name to constructor:
class tPlayer : public tObject<tPlayer>
{
public:
// constructor
tPlayer () : tObject<tPlayer> (PlayerMap) {};
}; // end of class tPlayer
// create an instance:
tPlayer * p1 = new tPlayer;
// is now in map:
cout << "Map count: " << PlayerMap.size () << endl;
// get id from pointer:
tId<tPlayer> i = p1->GetId (); // get player's ID
// get pointer back from id:
tPlayer * p = PlayerMap [i];
if (p) // if not null, it exists in map, thus pointer is valid
{
// do something with it
}
// delete any of the pointers - removes it from the map
delete p;
// not in map now:
cout << "Map count: " << PlayerMap.size () << endl;
*/
// handy typedef for 64-bit integers
#ifdef WIN32
typedef __int64 int64;
typedef unsigned __int64 uint64;
#else
typedef long long int64;
typedef unsigned long long uint64;
#endif
// the base type for our pointer identifiers
typedef uint64 id;
// get unique number (adds 1 every time)
uint64 unique (void);
// identifier for a class - this gives type safety over straight integers
template <class T>
class tId
{
protected:
id ID; // the identifier
public:
// default constructor for when we don't have an ID yet
tId () : ID (0) { };
// normal constructor - creates with supplied id
tId (const id i) : ID (i) { };
// copy constructor
tId (const tId & rhs) : ID (rhs.ID) { };
// operator= (assignment)
const tId & operator= (const tId & rhs)
{
ID = rhs.ID;
return *this;
};
// cast to id - return internal ID
// commented out because with it we lose some type-safety
// operator id () const { return ID; };
// return value (number) corresponding to internal ID
id Value () const { return ID; };
}; // end of class tId
template <class T>
class tObject;
// an owner of pointers
// it uses a map to store them, for fast-lookup purpose
// insertion and deletion is restricted to the friend class (the pointers)
template <class T>
class tPointerOwner
{
public:
typedef map<id, T *, less<id> > container_type;
typedef typename container_type::value_type value_type;
typedef typename container_type::size_type size_type;
typedef typename container_type::iterator iterator;
typedef typename container_type::const_iterator const_iterator;
protected:
// this is where we put them
container_type c; // container
public:
bool empty () const { return c.empty (); }
size_type size () const { return c.size (); }
iterator find (const id& key) { return c.find (key); }
const_iterator find (const id& key) const { return c.find (key); }
iterator begin() { return c.begin (); }
const_iterator begin() const { return c.begin (); }
iterator end() { return c.end (); }
const_iterator end() const { return c.end (); }
// operator [] gives us back the original pointer
T * operator[] (const tId<T> id)
{
iterator i = c.find (id.Value ());
if (i == c.end ())
return NULL;
return (T *) i->second;
}
protected:
friend class tObject<T>; // so they can erase from us
// insertion and deletion is protected so only our friend
// (the object who we are storing in the list)
// can delete or insert
void insert(const tId<T> item, T * ptr)
{
c [item.Value ()] = ptr;
};
void erase (const tId<T> item)
{
iterator i = c.find (item.Value ());
if (i != c.end ())
c.erase (i);
}
}; // end of class tPointerOwner
// object for a class
template <class T>
class tObject
{
public:
typedef tPointerOwner<T> tOwnerMap;
private:
tOwnerMap & m_owner_map; // the map that points to us
const tId<T> m_id; // our unique ID
public:
// constructor remembers which map points to us, gets unique ID
tObject (tOwnerMap & owner_map) // owning map
: m_owner_map (owner_map),
m_id (unique ())
{
// put ourselves into the map
m_owner_map.insert (m_id, reinterpret_cast <T *> (this));
}; // constructor
// destructor deletes us from the map
virtual ~tObject ()
{
m_owner_map.erase (m_id);
}
// return details about this object
tOwnerMap & GetMap (void) const { return m_owner_map; };
tId<T> GetId (void) const { return m_id; };
}; // end of class tObject
// safe_for_each. Apply a function to every element of a range.
// should handle correctly an element deleting itself
template <class ITER, class F>
F safe_for_each (ITER first, ITER last, F func)
{
while (first != last)
func (*first++);
return func;
}
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|