I recently had to write a bunch of C++ functions that shared a common pattern:
1) acquire a resource lock
2) TRY
3) do some work
4) CATCH exceptions of interest, and log them
5) release resource lock
I was eager to implement the "do some work" bits but did not want to bore myself silly by repeating the steps 1, 2, 4, 5 in each function (about thirty or so of them.) Now, you may recognize the problem, it is what the Template Method Design Pattern solves: "avoid duplication in the code: the general workflow structure is implemented once in the abstract class's algorithm, and necessary variations are implemented in each of the subclasses."
I could not however apply the Template Method pattern "as is" because my functions did not share a common signature. So wrapping them with a non-virtual function and then implement the "do some work" as virtual methods would not work in my case.
One alternative was to code steps 1 and 2 above as a BEGIN_CALL macro, wrap up steps 4 and 5 into a END_CALL and decorate each of my methods with these archaic C-isms. The approach would indeed work for C, but it is utterly indecorous in C++.
The spirit of the Template Method pattern can be preserved very elegantly by making the template method a C++ template method, and wrap the variant "do some work" bits into a lambda block.
The code sample below illustrates the idea (I added some mock objects to give more context).
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
// Mock a resource that needs exclusive locking
//
// -- think CComCriticalSection for example:
// http://msdn.microsoft.com/en-us/library/04tsf4b5(v=vs.80).aspx
class Resource
{
public:
void Lock() { cout << "Resource locked." << endl; }
void Unlock() { cout << "Resource unlocked." << endl; }
};
// Generic stack-based lock. Works with any resource
// that implements Lock() and Unlock() methods.
// See:
// http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
template<typename T>
class Lock
{
Lock(const Lock&); // non-copyable
Lock& operator=(const Lock&); // non-assignable
T& m_resource; // Lock MUST NOT outlive resource
public:
explicit Lock( T& resource ) : m_resource( resource )
{
m_resource.Lock( );
}
~Lock( )
{
m_resource.Unlock( );
}
};
/////////////////////////////////////////////////////////////////////////////
// Template Method Wrapper for an arbitrary lambda block:
// 1) lock a resource
// 2) log exceptions
// This is a variant of the Template Method Design Pattern
// -- implemented as a C++ template method.
template<typename F>
auto Execute(Resource& r, F f) -> decltype(f())
{
Lock<Resource> lock(r);
try
{
return f();
}
catch (const exception& e)
{
// log error
clog << e.what() << endl;
}
typedef decltype(f()) result_type;
return result_type();
}
/////////////////////////////////////////////////////////////////////////////
// Usage example:
static Resource globalResource;
int f (int i)
{
return Execute(globalResource, [&]()->int
{
return i + 1;
});
}
string g (unsigned j)
{
return Execute(globalResource, [&]()->string
{
ostringstream ss;
ss << '[' << j << ']';
return ss.str();
});
}
int main()
{
cout << f(41) << endl;
cout << g(42) << endl;
return 0;
}
Update: to compile the code above using GCC you may need to specify "-std=c++0x" on the command line.

0 comments:
Post a Comment