tags:

views:

134

answers:

3

I just fixed a memory leak caused by someone forgetting to call the superclass's OnUnload in their override of it. The superclass version frees some resources (as does its superclass).

Are there external static analysis tools, or at least some kind of runtime trick I can do to detect this? With the ability to make an exception obviously (though these cases are exceedingly rare).

UPDATE: Based on the answers below, I need to add constraints that are specific to our setup, which is a game for Wii/360/PS3. Very specific engine for a very specific application.

  • We have a deep hierarchy of game object classes (a design I never agreed with, but it's the design we are shipping). Deep and wide, actually. I am going to redo this for the next game to use a Dungeon Siege-style component-based system but in our current codebase, deep hierarchies make the DispatchVirtual() -> onVirtual() pattern difficult to apply.

  • Destructors do not get called for our game objects because delete doesn't get called. Game objects go into an add-only (stack) allocator-based pool during a world load. At the end of a level I just set the stack pointer back to the low water mark to free everything at once. In advance, we iterate all objects and call OnUnload on them so they can free any external resources they use. You might call it "garbage collection: the nuclear option". So no destructors.

  • Even if we could use a destructor-based approach it would only solve the narrow problem of an OnUnload or OnFree, but not OnUpdate, OnWorldMessage, OnLoaded, etc.

Runtime solutions are interesting but I hate relying on testing to catch this. Optimal would be either a compile-time template trick or an external static analysis tool I can use.

+4  A: 

A runtime "trick" you could use is to assert in the destructor of the base class if the constraint you are looking for has failed. Assuming the instance is actually destroyed and not leaked, this will tell you at the time the object is destroyed if the contract was correctly followed.

1800 INFORMATION
We aren't calling delete, but you know..I could just manually call the dtor. The compiler guarantees we go up the entire chain of objects. I can definitely use this to assert a few conditions at least. So this solves part of the problem, when releasing resources. Thanks! But I still need something for the other types of events (OnUpdate, OnLoaded, etc.).
Scott Bilas
+4  A: 

A "runtime trick" is to wrap your resources in a class and make sure that resource class handles allocation / deallocation appropriately. A major benefit you have in C++ over 3G languages is multiple inheritance.

xanadont
+6  A: 

Don't trust the derived classes to do it; use the template method design pattern to ensure your base class behavior will always happen:

class Base
{
   public:
      void OnUnload()
      {
          // Do stuff that must always be done.
          this->doOnUnload();
      }
   private:

      // Virtual method for derived classes to override.
      // Can be pure virtual if it will always be overridden.
      virtual void doOnUnload()
      {
         // Empty default implementation
      }
};

The only problem is that this only buys you one level of inheritance, and your problem says you need two. In which case, this pattern can be repeated.

But in general, it's usually more stable to have base classes call down to derived classes for specific behavior than to require derived classes to call up to base classes.

JohnMcG
We are using this exact pattern in our most-base class. But like you said, this only works per level of inheritance. So each derived version would need to add new virtuals. Our game object hierarchy is pretty deep (a design I disagree with, but it's the code we have) so this will end up too verbose and messy.
Scott Bilas
This needs a small tweak to provide run time checking:1. Add a private bool member variable to class Base (say baseUnloaded), and initialize it to false.2. In Base::doOnUnload(), set baseUnloaded true;3. In Base::OnUnload () do a runtime check that baseUnloaded is true. Voila, you're sure any derived version's override of doOnUnload() called Base::doOnUnload().This won't catch a deeply derived override of doOnUnload()deliberately skipping a level, but it will catch one that simply forgets to call its parent class version in its override.
Stephen C. Steel