Smart & Weak Pointers - valuable tools for games Charles Bloom, 3-27-04 I'm a big advocate of a proper Smart Pointer system. Designed & used right, it will save you tons of time managing your object lifetimes. First of all, you need a RefCounted base class. You can make smart pointers, like the std::auto_ptr<> that work on any class, but these are very dangerous because you must always use auto_ptr to point at them, and the ownership conventions are very unclear and scary. A Ref-Count is just counting how many people need the class, and when the count goes to zero, the object can go away because noone needs it. Having a RefCounted base means you can pass the object around naked, and you can manualy inc & dec the ref count when you need to (you shouldn't need to, but it's good to have the option). Now, you make a SmartPtr<> template that automatically takes a Ref on the object. This means that as long as you are holding a SmartPtr<> to an object, that object will never go away. It's important to think of this as *owning* the object, because you are preventing the object from dying. It's nice to be able to things like this : void Func(Object * obj) { SmartPtr ptr(obj); ptr->DangerousFunction(); ptr->MoreStuff(); } This gaurantees that "obj" doesn't go away while you're using it, no matter what DangerousFunction does. Note that obj might go away right at the end of the scope of this function when ptr gets destructed. Also note that this type of thing works really well with exceptions, because the refs get automatically fixed by the destructor unwinding. Also note that if obj == this, it keeps your own class from being destructed until the end of the function, which keeps you safe. For example : void Object::Func() { SmartPtr ptr(this); DangerousFunctionThatMightDeleteMe(this); MoreStuff(); } Keeping the SmartPtr in scope makes sure your "this" pointer isn't deleted while you're trying to use it. With SmartPtrs, classes can be singly or multiply owned. One very nice thing to do is to track the owners for debug purposes. The nicest way to do this is to keep a linked list on your RefCounted object which links to all the SmartPtrs that point at it. That way if you see a RefCounted that's hanging around when you think it shouldn't, you can walk the list and see who's owning it. If you combine this with a stack-tracer in the SmartPtr, you can get a stack of who assigned the SmartPtr. An example when you want to use SmartPtrs with multiple owners is for resources. My Texture class is RefCounted, then all the Geometry's that use a Texture have a SmartPtr to it. That way the Texture goes away exactly when all the Geometry's that use it stop needing it. You can also keep it in memory when noone needs it by having a global Manager that keeps an extra ref on the Texture. Now, many times you don't want "owning" semantics. For that, you need a Weak Reference, what's like a WeakPtr. These things are crucial you must have them in your game. Basically, the WeakPtr points at a RefCounted object, but doesn't keep it in memory, and if the object goes away, the WeakPtr knows it's now pointing at nothing. So, for example, game objects pointing at each other should always use Weak Pointers, since they are siblings on the ownership hierarchy. There are many ways to implement Weak Pointers that have different trade offs. On way is to keep a linked list of them off the RefCounted. The Weak Pointer can then store an actual pointer to the object, and the object can go away immediately when all the refs die. When the object goes away, it simply walks the link of WeakPointers and sets all the pointers to NULL. In this method the WeakPointer is 12 bytes, an actual pointer and 2 link pointers. Another common way is using a re-direct table. In this case, the WeakPointer stores a table index and also a GUID for that index. To access the weak pointer, it looks in the table and sees if the GUID matches its own; if they match, it returns the pointer in the table; if not, it returns NULL. Now, when your object goes away, all you do is increment the GUID in your table entry, which will invalidate all the weak pointers that point at you. In this method the WeakPointer is 8 bytes, a table index and a guid (though that can easily be just 4 bytes by making the index & guid 16 bit). When you use Weak Pointers you must always check them for NULL to make sure the object didn't go away. You can also hold temporary smart pointers if you don't want to deal with it for a while. For example : member WeakPtr m_talkie; void MyClass::StartTalking(Actor * toWho) { m_talkie = toWho; } void MyClass::ContinueTalking() { if ( m_talkie == NULL ) { // guy I'm talking to went away ! CancelTalking(); return; } // hold a ref while I work so I don't have to keep checking if m_talkie went away SmartPtr talkie(m_talkie); // ... do work ... } You can also use Weak Pointers to make sure your ref-tracking is working, for example : member SmartPtr m_texture; void MyClass::ReleaseTexture() { WeakPtr weak = m_texture; m_texture = NULL; ASSERT( weak == NULL ); } in this ReleaseTexture function, the weak pointer will automatically go to NULL if you released the last reference. If weak is not NULL, you can use it to get at the object and use your linked list and stack traces to see who is holding the remaining references. Something you have to be careful of are cycles of Smart Pointers. This is the standard Garbage Collection problem - if object A owns B, and B owns A, they will both stick around forever, even though noone really needs them. The solution is to use a proper tree-structured graph of ownership. You should have a hierarchy of what classes can own other classes. Any class at a certain level can only use Weak Pointers to classes at the same level. For example : World -> GameObject -> Geometries -> Textures establishes a strict order of who can own who. To avoid Cycles and "ref leaks" it's good practice to use the actual SmartPtr<> as rarely as possible!! Use WeakPtr<> as much as possible, an whenever in doubt. Passing around your objects with naked pointers is also fine. Member variables and storage should always ber a SmartPtr or WeakPtr, but there's nothing wrong with using "naked" (plain C) pointers for temp/stack objects. Another little wrinkle is the case of pooled object, or objects you wish to recycle for efficiency reasons. For example, you might have allocated 64 SoundVoice objects, and when they are recycled, you want to simply re-use the previous rather than delete it and make a new one. Of course, you don't want any pointers to keep pointing at your object, since it is really acting just like a new one. So, first of all make sure you own the only ref : ASSERT( obj->GetRefCount() == 1 ); And then imagine what would happen if you actually re-made the object : ptr = NULL; ptr = new SoundVoice(...); Well, this resets the object, and it also releases all the WeakPointers ! So, you can emulate this just by manually forcing the weak pointers to be released, so instead you would do something like : ptr->ResetMembers(); ptr->ClearWeakPointers(); where ClearWeakPointers does whatever work is done to the Weak Pointers when an object is deleted. Have fun!