Sunday, August 30, 2009

A Weak Smart Pointer, or a Smart Weak Pointer

Smart pointers simplify many lifetime management scenarios, especially when designing modular components and systems with a multitude of lifetime dependency options available to final applications. Weak pointers are also useful, but are flawed in a multithreaded environment.

The other day a hybrid smart/weak pointer occurred to me, I'll present it here.

A refresher:
  • A smart pointer is an object that acts like a native pointer, but whose lifetime controls a dynamically allocated object's lifetime. As long as you hold a smart pointer to an object, it continues to live. Multiple smart pointers can point to one dynamically allocated object, and it will not be deallocated until all smart pointers are released. Implementations typically maintain a reference count and delete the dynamic object when that ref count goes to zero.
  • A weak pointer is an object that acts like a native pointer, but whose value is changed to NULL if the object pointed to is deallocated. Holding onto this type of pointer does not keep an object alive, but you can check it's value and see that it is NULL when the object has been destroyed (a normal pointer would still point to the memory the object used to reside in).

Discussion:
Smart pointers are sometimes used for code safety, even when a system does not desire lifetime management responsibility. Weak pointers are a better solution here, allowing a system to keep a reference that will go NULL when the object dies.

In multithreaded systems weak pointers lose their benefit if the dynamic object can be freed from another thread. A portion of code may check the value of the weak pointer and find the object exists. However, as the object is accessed another thread may free it.

One work around is to use a short lived smart pointer, which will keep the dynamic object alive from the weak pointer dereference until work is complete. This requires a smart and weak pointer pair implementation designed to support this use case in a multithreaded system, and is also likely quite a performance burden due to the additional reference count manipulation.

For code that runs infrequently, the smart & weak pointer pair is a good option though. Without any smart or weak pointers the system would need to implement thread-safe API to register and deregister objects, and the systems would need to be tightly coupled (or perhaps use a message system).

My Idea:
A smart weak pointer to the rescue? It would be nice to have the same functionality of a smart pointer, knowing that as long as you hold it the object will live, but also indicate to the system that you're willing to let the object die. The only catch is that you need to let the object die at a convenient time for you, so that you can safely handle the situation, but not be required to safely handle it everywhere you use the object.

Start with a smart pointer implementation. Add an additional ref counter for the smart weak pointers, call it WeakRef. Any smart pointer pointing to an object acts as before. Any smart weak pointer acts the same as a smart pointer, but also increments WeakRef counter. Add a method to check the ref and WeakRef counters to see if they are equal, and if so set the smart weak pointer to NULL and decrement the two counters.

A system then could use the smart weak pointers just as if they were typical smart pointers, and occasionally check if the smart weak pointer should release it's reference.

System::OnTick()
{
for (lots of work to do)
{
Look up the right smart weak pointers to dereference, and use them as normal
}

for (all smart weak pointers)
{
CheckForRelease(p)
}
}
This works out well for a multithreaded system that accesses a large number of smart pointers frequently, and has a regular opportunity to release them.

If your system only infrequently dereferences pointers, is single threaded, or performance isn't an issue; the discussion's example of weak pointers with temporary smart pointers would be great for you.