Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

AutoRunner: a template class to automatically run start- and cleanup-code in code blocks

0.00/5 (No votes)
19 Jun 2003 1  
The presented template class offers an easy way to create simple classes that handle the initialisation and cleanup of 'logic' in code blocks.

Introduction

Sometimes you want to automatically execute some code at the end of your code block, to free memory, free resources, unlock files, ... Some of the standard cleanup methods have already been foreseen by the C++ standard, STL or your C++ compiler. e.g. Auto-pointers (or smart-pointers) provide a way of making sure that your memory is freed at the end of your code block.

How such an automatic cleanup is implemented

The automatic cleanup is implemented by a separate class, whose destructor automatically performs the necessary cleanup. e.g. The following code shows the definition of a class that performs file locking. The constructor will automatically lock the file. The destructor will unlock it.

class FileLock
   {
   public:
      FileLock (char *FileName);
      FileLock (char *FileName, long Timeout);
      ~FileLock ();
   private:
      HANDLE m_Handle;
   };

Using such a class is quite simple. To lock a file, make an instance of the class; to unlock it, destroy the instance.

void myFunction ()
{
FileLock fileLock("myfile.txt");
...
// file is automatically unlocked if fileLock goes out of scope

}

What if cleanup is not performed in a destructor?

Suppose we have a class that contains (besides many other methods) the following two methods: increment and decrement. If a function or method calls increment(), it is also responsible for calling decrement at the end, like shown in this example:

void myFunction (MyClass &myClass)
{
myClass.increment();
...
myClass.decrement();
}

Now, what if the function forgets to call decrement at the end of the function? Or what if an exception is thrown in the function? Yep, MyClass will probably go bananas, incorrect data will be shown by the application, it will crash, ... The solution is simple, but cumbersome. Simply write a class in which the constructor will call increment, and the destructor will call decrement, like this:

class MyClassIncrementer
   {
   public:
      MyClassIncrementer(MyClass &myClass) : m_myClass(myClass) 
      {m_myClass.increment();}
      ~MyClassIncrementer() {m_myClass.decrement();}
   private:
      MyClass &m_myClass;
   };

Now instead of calling increment and decrement ourselves, we simply use this new class:

void myFunction (MyClass &myClass)
{
MyClassIncrementer myClassIncrementer(myClass);
...
}

At the end of the function, myClassIncrementer will automatically be destructed, which will trigger myClass.decrement().

Should I write such a new class over and over again?

No, by using templates we can easily hide this functionality in templates.

Static methods or global functions

First we write a simple template that will handle the automatic calling of global functions or static methods. The implementation looks like this:

template <void (*SF)(), void (*EF)()>
class AutoRunnerStatic
   {
   public:
      AutoRunnerStatic  () { SF();}
      ~AutoRunnerStatic () { EF();}
   };

Two function pointers are passed to the template. The first one (StartFunction) will be called in the constructor. The second one (EndFunction) will be called in the destructor.

Suppose we have the following global functions:

void FirstAction() { std::cout << " FirstAction" << std::endl;}
void LastAction () { std::cout << " LastAction"  << std::endl;}

Imagine that these functions do something really useful here. Since this is an example, they will only print some output.

Suppose that we mandate that if we call FirstAction in a routine, EndAction should also be called at the end of the routine. To simplify the use in the application, we typedef a shortcut.

typedef AutoRunnerStatic<FirstAction,LastAction> AutoAction;

Now the application is ready to use 'AutoAction', like this:

int main()
{
std::cout << "In beginning of main" << std::endl;
   {
   AutoAction autoAction;
   std::cout << "  In sub block" << std::endl;
   }
std::cout << "At end of main" << std::endl;

return 0;
}

The following output will be printed:

In beginning of main
 FirstAction
  In sub block
 LastAction
At end of main

OK, that solves our problem for global functions, what about non-static methods?

Non-static methods

The template to obtain the same effect for non-static methods is a bit complex. Also Visual C/C++ 7 (possibly 7.1) or GNU C++ is required for this (it does not compile in Visual C/C++ 6 !!!).

template <class T, void (T::*SF)(), void (T::*EF)()>
class AutoRunner
   {
   public:
      AutoRunner  (T &instance) : m_instance(instance) { (m_instance.*SF)();}
      ~AutoRunner () { (m_instance.*EF)();}
   private:
      T &m_instance;
   };

Also here the methods SF and EF are automatically called in respectively the constructor and the destructor of the template class.

The real challenge in this template was to put all brackets, asterisks and parenthesis correctly (which wasn't simple).

Suppose we have the following class with the methods FirstMethod and SecondMethod. Similar to the global functions, we mandate that the caller of FirstMethod, should also call LastMethod.

class MyClass
   {
   public:
      MyClass (long value) : m_value(value) {}
      void FirstMethod() 
      { 
        std::cout << " FirstMethod, value is " << 
        m_value << std::endl;
      }
      void LastMethod () 
      { 
        std::cout << " LastMethod , value is " 
                << m_value << std::endl;
      }
   private:
      long m_value;
   };

The writer of MyClass also foresees the following typedef to make it easier for the user of that method:

typedef AutoRunner<MyClass,&MyClass::FirstMethod,
        &MyClass::LastMethod> MyClassAuto;

Now the MyClassAuto class can easily be used to make sure that LastMethod is called whenever FirstMethod is executed, like shown here.

int main()
{
std::cout << "In beginning of main" << std::endl;

MyClass myInstance(1);
MyClass myInstance2(2);

   {
   MyClassAuto myClassAuto(myInstance);
   std::cout << "  In sub block" << std::endl;
   }

   {
   MyClassAuto myClassAuto(myInstance2);
   std::cout << "  In sub block" << std::endl;
   }

std::cout << "At end of main" << std::endl;

return 0;
}

This will print the following output:

In beginning of main
 FirstMethod, value is 1
  In sub block
 LastMethod , value is 1
 FirstMethod, value is 2
  In sub block
 LastMethod , value is 2
At end of main

And this proves that FirstMethod and LastMethod are called correctly.

const methods

After having written these two nice templates, I started using them right away in a new class I wanted to write. My class had two methods - lock() and unlock() - that locked and unlocked a data structure (for use in a multithreaded environment). My class was called ThreadSafeList (maybe more of this in a later article).

What I did was defining a Locker class like this:

typedef AutoRunner<ThreadSafeList,lock,unlock> Locker;

My methods then simply had to create a Locker on *this like shown here:

void clear () {Locker locker(*this); stltype::clear();}

And indeed this correctly worked ... until I wrote a const method, like this:

bool empty () const {Locker locker(*this); return stltype::empty();}

The compiler threw the following messages at me:

error C2440: 'specialization': 
cannot convert from 'void (__thiscall ThreadSafeList<T>::* )(void) const'
to 'void (__thiscall ThreadSafeList<T>::* )(void)'

The AutoRunner template clearly does not like const instances. The solution however is very simple. We write a const variant of the template, like this:

template <class T, void (T::*SF)() const, void (T::*EF)() const>
class AutoRunnerConst
{
    public:
       AutoRunnerConst (const T &instance) : m_instance(instance) 
       { (m_instance.*SF)();}
       ~AutoRunnerConst () { (m_instance.*EF)();}
    private:
       const T &m_instance;
};

We simply made lots of the stuff const in the template. The two methods, the private data member, and the argument of the constructor are all made const. We only need to modify our typedef to this:

typedef AutoRunnerConst<ThreadSafeList,lock,unlock> Locker;

And we're ready. Well, not quite, we still get the following compiler errors:

error C2664: 'EnterCriticalSection' :
cannot convert parameter 1 from 'const CRITICAL_SECTION *'
to 'LPCRITICAL_SECTION'
Conversion loses qualifiers
while compiling class-template member function 
  'void ThreadSafeList<T>::lock(void) const'

This message means that the lock method tries to modify my data member that manages the locking (in this case a CRITICAL_SECTION) and this is simply not allowed in a const method.

So the last step we need take, is to make our critical section mutable, like this:

mutable CRITICAL_SECTION m_criticalSection;

Et voila, it works.

Disadvantages and improvements

The biggest disadvantage is that the methods passed to the template cannot have arguments. That can probably be solved by adding more template classes, but I did not wanted to go into that.

Alas, the second and third templates do not compile in Microsoft's Visual C/C++ 6.0, although they compile correctly in Visual C/C++ 7.1 (not sure about 7.0). This shows that 7.1 will be a much better compiler than 6.0.

Another improvement might be to make the methods that should be called in pairs (in my example FirstMethod and LastMethod) private in the class, and making the template class a friend of it. That guarantees that nobody else calls FirstMethod and/or LastMethod explicitly, with the danger of not correctly executing them as a pair.

Do you like templates?

This article is yet again a demonstration on how templates can be used to solve problems in which you have to write the same type of class over and over again. Simply write the template (once!), then use it (or typedef it to make it easier for the caller).

History

  • 28 March 2003: Original version
  • 20 June 2003: Added AutoRunnerConst

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here