views:

314

answers:

3

Is there a simple, efficient weak/guarded pointer? I need multiple pointers to the same object that are all automatically set to NULL when the object is deleted. There is one "master" pointer that is always used to delete the object, but there can be several other pointers that reference the same object.

Here are some solutions that don't quite match my needs:

  • QPointer: I am not developing a QT app; I do not wish to include this libary/derive from QObject.
  • boost::weak_ptr: an exception is thrown when accessing a deallocated object. Too expensive for my situation: it should be normal to test a weak pointer; I plan to do some manual clean-up when a weak pointer is no longer valid. update:weak_ptr can be tested without throwing exceptions
  • Low-Overhead Weak Pointers: This is very close to what I am looking for, except I don't like the fact "This scheme is only guaranteed to work as long as you don’t allocate 2**sizeof(int) times in the same location."

Why I need these weak/guarded pointers: I have a game with a list of game objects. Some objects are dependent on others, for example a debug/stats object that is associated with a game entity. The debug/status object displays useful info about the game entity, but it only makes sense while the game entity exists. So if the game entity is deleted, the debug/stats object should realize this and delete itself. (Another idea is a tracking missile: instead of deleting itself, it may search for a new target.)

I wish to keep the debug/stats logic separate from the game entity. The game entity should not have to know a debug/stats object is attached to it. While I'd prefer an answer for weak/guarded pointers, I also welcome different ways to approach my specific task. I am thinking I may have to implement a game object manager that tracks object lifetimes and uses handles instead of raw pointers to memory addresses.

I am developing in C++.

+13  A: 

You can use the lock() member of boost::weak_ptr to be able to test (then use) the value of the weak_ptr without dealing with exceptions.

Michael Burr
+2  A: 

Well, a really basic way to do this is to use a pointer to a pointer. The pointer you point to points to the actual object. When you de-allocate that object, you set that pointer to NULL, and since every reference to that object is actually a refrence to the pointer to that object (which is now NULL) they all can tell it has been de-allocated.

an example:

MyObject *objectPointer = new MyObject();
MyObject **objectPointerPointer = &objectPointer;

// check the object hasn't been dallocated
if ( *objectPointerPointer != NULL )
{ 
    // do stuff
}
else
{
    // handle object no longer existing
}

// deallocate the object
delete *objectPointerPointer;
*objectPointer = NULL;

Edit: I would also recommend encapsulating this as a class so it isn't so ugly and confusing. Also you would need some locking mechanism if more than one thread does this.

Nali4Freedom
+3  A: 

This is a common thing in game development. Typically a system of object handles is used rather than Boost weak pointers, because we need the underlying lookup table to be constant memory and because sometimes we need some additional information or guarantees that Boost hasn't got.

The usual approach is to use an elaboration on pointers to pointers. An entity is referred to by handle rather than by pointer. The handle is an index into a big array of pointers to entities. When an entity dies, it NULLs out the pointer in its entity table.

struct handle_t
{
   uint32 serialnumber;  // this is a GUID for each entity; it increases 
                         // monotonically over the life of the process
   uint   entityindex;
   inline Entity *Get();
}

struct entityinfo_t
{
   Entity *pEntity;  // an entity's destructor NULLs this out on deletion
   uint32  serialnumber;
}

entityinfo_t g_EntityTable[MAX_ENTITIES];

Entity *handle_t::Get() 
{
  entityinfo_t &info = g_EntityTable[entityIndex];
  if ( serialnumber == info.serialnumber )  
  {
     return info.pEntity;
  }
  else
  {
      return NULL;
  }
}

The serial number is necessary because the array is of constant size -- eventually, you will need to recycle entity table entries, and there's a possibility that you might store a handle to, say, index #743, long enough that the object gets deleted and cell #743 reused for something else. If you simply had a pointer to a list of pointers, you would end up having a handle that points to an entirely different object rather than going NULL. So, we give each entity a globally unique number and store that in the handle as well.

You could use a std vector or a map or a dictionary or some other kind of data structure for the entity table, of course, but our requirements have typically been for constant memory, cache coherency, and absolute maximal performance (since handle_t::Get() gets called thousands of times per frame).

Crashworks