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");
...
}
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