Re: Locking arbitrary resources without creating objects on the heap (longish)
Joshua Lehrer wrote:
You can do this without a heap based object.
First, you need a base type, call it BaseLock:
struct BaseLock { };
next you need a templated subclass. Note that I have not
compiled/tested this code, this is from memory:
teplate <typename T>
struct LockClass : public BaseLock {
explicit LockClass(T& lock) : m_lock(lock), m_enabled(true) {
m_lock.lock();
}
~LockClass() {
if (m_enabled) m_lock.unlock();
}
LockClass(const LockClass &rhs) : m_lock(rhs.m_lock),
m_enabled(rhs.m_enabled) {
rhs.m_enabled=false;
}
//hold a reference to the lock
T& m_lock;
//only one owner of the 'lock' at a time
mutable bool m_enabled;
};
And a constructor function:
template <typename T>
inline LockClass<T> make_lock(T& lock) {
return LockClass<t>(lock);
}
This is a good solution. There is a small drawback - although you could
have more derivatives of BaseLock that use methods other than a member
unlock(), a large part of your code here has simply been implementing
move-semantics. So, for example, if I want to implement (someday in the
future when not all my classes use unlock() as their method) an
implementation of BaseLock then I would need to implement the move
semantics rather than the simple one-liner (maybe wrapped in an
operator() or a destructor) that calls the appropriate method.
A policy-type based template might be useful here - a generic template
for move-semantics and another that implements the call to unlock.
Now, to use it:
First:
typedef const BaseLock & GenericLock;
now you can do:
GenericLock lock = make_lock(mymutex);
Good.
Better, but it would be nice if you could automatically create a region
as well.
I've even gotten this to work:
LockRegion(mymutex) {
//locked code here
}
To get that to work, you need to add a test to the base class so it can
be tested:
struct BaseLock {
operator bool() const { return false; }
};
and a macro:
#define LockRegion(x) if (const BaseLock & lock = make_lock(x) { } else
With all the drawbacks of macros.
Firstly, there is a syntax error in that macro - you forgot to close a
bracket. Unfortunately your compiler won't show this error until you
try using it for the first time (which may be not now) and when it does
the error won't be nice and comprehensible - i.e. it will show up in
what looks like a valid line of code.
Second drawback is that it creates a symbol called "lock" that the user
doesn't know about. It might hide another symbol of the same name- not
so unlikely - or the user of your macro may try to create another
symbol called lock inside the block and will get a compiler error (less
fatal than when it hides a symbol and the code could then well compile
with unexpected behaviour).
Although you could get around this by using a name that the user is
highly unlikely to use, something like
baselock_scoped_lock_object_10293, there is a danger that the user may
try to nest these blocks and unfortunately I don't know of a way around
that.
I'd rather use the slightly less tidy syntax of
// some code
{
GenericLock lock = makelock( myMutex );
// the region
}
If you really want to use a macro at the head of that block put one
that defines something like:
#define SCOPED_BLOCK if(false) {} else
but is that really necessary? A C++ programmer should understand what
the braces in my code are for.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]