Click here to Skip to main content
Click here to Skip to main content

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

By , 19 Jun 2003
 

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

About the Author

Patje
Software Developer
Belgium Belgium
Member

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralErrors.memberWREY4 Oct '04 - 4:46 
Two errors were obtained when I tried compiling a sample of the code I downloaded.
 
error C2973: 'AutoRunner' : invalid template argument 'SF'
 
The other error is the same for 'EF'
 
==================================================
 
template <class T, void (T::*SF)(), void (T::*EF)()>
class AutoRunner
{
...
}

 
William
 
Fortes in fide et opere!
GeneralRe: Errors.memberPatje4 Oct '04 - 6:16 
As said in the article, the code only compiles with Visual Studio .NET (7.1).
It doesn't compile with Visual Studio 6 because that version has only limited template support.
It's possible that it also compiles with the GNU compiler, I didn't check that recently.
 
Thanks for downloading the code.
 
Enjoy life, this is not a rehearsal !!!


GeneralYes, you are correct.memberWREY5 Oct '04 - 9:48 
It runs well with VC7, and it's my VC6 compiler that's incapable of handling it the way it's outlined in the sample.
 
Thanks, nonetheless. I'm going to try all sort of hacks until I find a way of using this wonderful sample.
 
A '5' nevertheless from me.
 
Big Grin | :-D
 
William
 
Fortes in fide et opere!
GeneralSmart Pointer Programming TechniquesmemberNeville Franks28 Mar '03 - 10:41 
Nice article, thanks. Getting auto cleanup using dtor's is a good technique and something I've been using for a while.
 
You mentioned smart pointers. For folks interested in reading more about this have a look at "Smart Pointer Programming Techniques" http://www.boost.org/libs/smart_ptr/sp_techniques.html[^]
 
Boost shared_ptr lets you specify a custom deleter. Several examples of this are present in this article including: "Using shared_ptr to execute code on block exit".
 
And finally have a look at ScopeGuard http://www.cuj.com/experts/1812/alexandr.htm?topic=experts[^]
 
Happy reading.
 
Neville Franks, Author of ED for Windows. www.getsoft.com
Make money with our new Affilate program
GeneralRe: Smart Pointer Programming TechniquesmemberWilliam E. Kempf20 Jun '03 - 10:38 
Two other things from Boost will solve several issues in this article. First, Boost.Function (http://www.boost.org/doc/html/function.html) provides a replacement for those function signatures which lead to problems with member functions. Further, it works for VC6. Second, Boost.Bind (http://www.boost.org/libs/bind/bind.html) or Boost.Lambda (http://www.boost.org/libs/lambda/doc/index.html) can be used (assuming you use Boost.Function instead of sticking with the hard code function signatures) to work around not being able to pass parameters.

 
William E. Kempf
GeneralRe: Smart Pointer Programming Techniquesmemberpeterchen5 Sep '03 - 13:14 
Neville Franks wrote:
And finally have a look at ScopeGuard
 
Ah! you beat me to it! Roll eyes | :rolleyes:
 

"Dor säggsische Dialeggt eechnet sich wie keeen onderor für den Ausdrugg zäärdlischor Gefiehle."

sighist | Agile Programming | doxygen

GeneralAnother suggestionmemberJoaquín M López Muñoz28 Mar '03 - 5:34 
I think there's a small flaw in your code: as you haven't defined copy constructors and assignement operators for AutoRunner and AutoRunnerStatic, they will get default ones, which is in contradiction with the intended semantic, as this sample shows:
MyClassIncrementer myClassIncrementer(myClass);
MyClassIncrementer anotherClassIncrementer(myClassIncrementer);
You'll get one increment and two decrements this way. I guess the most sensible solution is to ban copy and assignments of these objects by simply declaring their copy constructor and assignment operator private (you need not actually define them).

 
Joaquín M López Muñoz
Telefónica, Investigación y Desarrollo
GeneralRe: Another suggestionmemberPatje28 Mar '03 - 5:39 
Yep, you're right.
I always forget looking at copy constructors and assignment operators (shame on me).
I will correct this when I update the article.
 
What do you think is best regarding copy constructors and assignment operators:
  • Always make them private (without implementation) unless you really need them and then provide a decent implementation or check whether the default implementation works correctly.
  • Always think about providing a correctly working copy constructor and assignment operator (or use the default)
    ???
     
    Enjoy life, this is not a rehearsal !!!
     
    My Articles:
    - Implementing a Subject/Observer pattern with templates
    - Different ways of writing class factories
    - AutoRunner: a template class to automatically run start- and cleanup-code in code blocks
  • GeneralRe: Another suggestionmemberJoaquín M López Muñoz28 Mar '03 - 5:49 
    What do you think is best regarding copy constructors and assignment operators:
    * Always make them private (without implementation) unless you really need them and then provide a decent implementation or check whether the default implementation works correctly.
    * Always think about providing a correctly working copy constructor and assignment operator (or use the default)

     
    Ufff... Well, I'd say that the semantics of a given class almost always dictate what to do about copying and assignment --be it using default behavior, provide your own or ban it. What's wrong is not thinking about it. So, design your class, think about what you want to them to behave when copying and assigning around and make your choice.
     

     
    Joaquín M López Muñoz
    Telefónica, Investigación y Desarrollo
    GeneralRe: Another suggestionmemberNemanja Trifunovic28 Mar '03 - 11:41 
    Patje wrote:
    What do you think is best regarding copy constructors and assignment operators:
     
    That depends on the class design. If your class is intended to be a concrete ("value") type, then by all means provide both copy constructor and assignment operator (or leave the default ones if that is what you want). If it is a part of some OO hierarchy, than you will probably want to make them private, without implementation.
    GeneralFunctors again - STLmemberJonathan de Halleux28 Mar '03 - 4:09 
    With functors, you could also handle nicely the non-static methods. STL templates like mem_fun, mem_fun_ref, etc... can handled that:
     
    class Widget
    {
        int test();
    }
     
    // creating a functor
    mem_fun_t functor( &Widget::test ) ;
     
    Widget* p;
     
    // call p.test();
    functor(p); 
    
     
    I'll wait before giving a 5...
    GeneralNice - to fix arguments - use functorsmemberPeter Hancock28 Mar '03 - 1:50 
    Nice article. I like the template idea.
     
    I use this pattern for transaction control to ensure that transactions either commit or rollback.
     
    To use arguments, you could pass functors, (function objects implementing operator() ), instead of function pointers. This way, you can actually write all the arguments and pass them in in the constructor. The usage essentially remains the same.
     
    Andrei Alexandrescu has something about function pointers and functors in Modern C++ Design.
     
    5 from me. I like anything that uses templates intelligently.
     
    And they still ran faster and faster and faster, till they all just melted away, and there was nothing left but a great big pool of melted butter
     
    "I ask candidates to create an object model of a chicken." -Bruce Eckel
    GeneralRe: Nice - to fix arguments - use functorsmemberGary Wheeler28 Mar '03 - 7:48 
    Peter Hancock wrote:
    "I ask candidates to create an object model of a chicken." -Bruce Eckel
     
    I like your Eckel quote. He's a cool guy. I took an intro to C++ course from him a few years ago, and learned a lot. His books are great, too.
     

    Software Zen: delete this;

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Permalink | Advertise | Privacy | Mobile
    Web01 | 2.6.130523.1 | Last Updated 20 Jun 2003
    Article Copyright 2003 by Patje
    Everything else Copyright © CodeProject, 1999-2013
    Terms of Use
    Layout: fixed | fluid