|
Boost : |
From: Andy Glew (glew_at_[hidden])
Date: 2000-01-06 14:09:32
> Totally understand. I would point out, though, that synchronization is a
> kind of thing that has to be invasive (one could write synch_ptr wrapper around
> shared_ptr using mutexes...
A challenge:
It sure would be nice if we could write, in C++, a template
mutexed<T>
that ensured full mutually exclusion for any arbitrary type T.
That did not have to know about the methods of T.
I was about to start my usual moaning about C++ not really
allowing transparent proxying - to fully proxy something in C++
you need to enumerate all of its methods - but I think that we might
be able to come close.
Here's a stab at it using some helper macros. (Yeah, well, I hope
that someone can eliminate the macros.)
template<typename T>
class mutexed {
private:
lock_t lockvar; // any lock you want - I'll assume just a lock bit
// handled by primitive processor instructions
T value;
public:
void acquire_lock() { lock.acquire_lock(); }
void release_lock() { lock.release_lock(); }
public:
// constructors don't require locks on constructee
mutexed() :
lock(0), // default unlocked
value() // must have a default constructor;
// must be constructible without locking
{}
mutexed(const mutexed& from) :
lock(0),
value()
{
from.lock.acquire_lock();
this->value = from.value;
from.lock.release_lock();
// or, use constructor locking, if you want locks released on exceptions
}
public:
// assignment requires locks on both sides
T& operator=(mutexed<T>& rhs) {
assert(this->lock);
{ rhs.lock.acquire_lock();
this->value = rhs.value;
rhs.lock.release_lock();
// or, use constructor locking, if you want locks released on exceptions
}
}
public:
// destructor - checks lock?
~mutexed() {
assert(lock == 0); // your choice
}
public:
// only allow access via ->
// which checks lock
// (if I could have -> enforce lock, I'd use that)
T* operator->() {
assert(lock);
return this;
}
};
Finally, the macro to access methods
#define MUTEX_ACCESS(muT, method) \
{ (muT).acquire_lock(); (muT)->method; (muT).relase_lock(); }
This macro is a statement, not an expression.
If you are willing to use GNU C++'s non-standard statement expressions,
and use "constructor locking" (acquire the lock in construction of a variable,
released automatically when destroyed), via something like an "auto_lock"
analagous to an "auto_ptr<T>" (with appropriate changes to mutexed<T>
#define MUTEX_ACCESS(muT, method) \
({ auto_lock lock_tmp((muT).lock); \
(muT)->method;})
This can be used as an expression.
Caveat: on an exception, the lock would be released - which may not be
what you want. Catching the exception would make this no longer usable as an
expression.
(Note that parenthesizing method in these macros will not always work.)
Given something like this, you could do
mutexed<your_favorite_class> obj1, obj2;
obj1 = obj2; // mutexed implicitly
obj1->method(); // runtime error, not mutexed
obj1->member = 11; // runtime error, not mutexed
MUTEX_ACCESS( obj1->method() ); // mutexed
I was hoping that you could do just
mutexed<T> mobj;
mobj->method(); // implicitly mutexed
although I don't see how to do this
The above assumes that the lock is a simple hardware spinloop
binary lock. It would be straightforward to extend this to OS
level queuing locks.
I'm not quite sure how, but if we could distinguish const mutexed<T>&
from mutexed<T>& returns, then you could implicitly use multireader
/ single writer locking. If C++ allowed polymorphism based on
return type... but it doesn't.
--- Anyway: auto_lock<class LockType> may be a useful library addition. e.g. auto_lock<binary_spinlock> auto_lock<queueing_lock> etc. And if a clean mutex<T> can be written, that would be nice too.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk