2012-03-22

Differential EnScript

My former colleague Jamie Levy released a new EnScript today, for doing differential analysis of hard drives. You should check it out.

2012-03-21

Don't call virtual functions in a destructor

Sometimes I like to define the outline of an operation in a base class and then have it delegate to virtual functions in derived classes. This is the Template Method design pattern.

class Base {
  uint Counter;


  void foo() {
    ++Counter;
    _foo();
  }


  pure void _foo();
}


class Derived {
  virtual void _foo() {
    Console.WriteLine("In Derived::_foo");
  }
}



Last night I tried to gin up a sort of "stack guard," something that would let me perform an operation once I got to a certain point in a function, but which also would be executed if the function returned early. When something needs to be done at the end of a block of code, I usually define a new class that has code in its destructor. For example, a lock class could take a critical section and acquire it in its constructor and release it in its destructor, and you just need to create the object in the appropriate place to perform your thread-safe operation. I wanted the same thing, but just a bit more complex, where I could call the release function manually once I'd reached a certain point and then be assured it would not be called in the destructor.

class Committer {
  bool Committed;


  ~Committer() {
    if (!Committed) {
      commit();
    }
  }


  void commit() {
    _commit();
  }


  pure void _commit();
}


class SafeDB: Committer {
  SafeDB(Database db): Committer() {
    // open a database connection
  }


  virtual void _commit() {
    // commit any pending transactions
  }
}



Simple enough. You can quibble about whether it's really worth it to define a class hierarchy for such simple functionality, but I've been trying hard to increase the amount of abstraction I use in EnScript to reduce code bloat and time wasted debugging.

The problem is that this doesn't work and generates run-time errors about null references. Why?

Answer: SafeDB's destructor is called first, and then Committer's destructor is called. As part of executing SafeDB's destructor, it nulls out whatever class members are a part of SafeDB before calling Committer's destructor. Hence, by the time you get to Committer's destructor, you really only have a Committer object, not a SafeDB object. The virtual table is still active, however, so the call to SafeDB::_commit() happens successfully, but it finds itself confronted with no class variables. Boom!

So, this is now another little corner of the EnScript world that's been explored. Time to light out for the territory...