Locking arbitrary resources without creating objects on the heap (longish)
Hi,
suppose you have several different resources, all wrapped in
different classes, which all have 'lock()' and 'unlock()'
member functions. (A mutex would be a common example of such
a resource.) Of course, in order to be exception safe you
want to lock in some ctor and unlock in a dtor, so you need
to create lock classes for those resources. For the users,
the best would be to have the same lock class for all those
resources. Since they are different, non-related types, some
template-thingie will be required.
I have been thinking about this for a while and had a hard
time to come up with a solution. It's easy to setup something
that involves a dynamic object. Here's an outline of this:
class lock {
private:
struct locker_base {
virtual ~locker_base() {}
};
template< typename T >
struct locker : public locker_base {
locker(const T& obj) : obj_(obj) {}
~locker() {obj_.unlock();}
const T& obj_;
};
public:
template< typename T >
lock(const T& obj) : locker_(new locker<T>(obj) {}
~lock() {delete locker_;}
private:
locker_base* locker_;
};
The class 'lock::locker<T>' would store a reference to the
resource object and call the resource's 'unlock()' function
in its dtor.
This works, but has the disadvantage of creating an object
on the heap -- which might be OK for most resources, but
for some (de-)allocation/deallocation might dominate the
(un-)locking run-time.
So I came up with the code attached below. It assumes that,
for every possible type 'T', 'lock::locker<T>' has the same
size. (It also assumes that 'lock::locker<T>' has the same
alignment as a 'char' array. I think I know how to fix that,
but thought it would only blurr the picture here.)
I am sure I am deep into UB here, but how portable is this?
Can I assume this to work as expected on most current
platforms? Are there any other problems I didn't see?
TIA,
Schobi
/Code follows
--8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--
#include <iostream>
class lock {
private:
struct unlocker_base {
virtual ~unlocker_base() {}
virtual void unlock() = 0;
};
template< class T >
class unlocker : public unlocker_base {
public:
unlocker(T& obj) : obj_(obj) {obj.lock();}
virtual void unlock() {obj_.unlock();}
private:
unlocker(const unlocker&);
unlocker& operator=(const unlocker&);
T& obj_;
};
public:
template< class T >
lock(T& obj) {new(buffer) unlocker<T>(obj);}
~lock()
{
unlocker_base* ptr = reinterpret_cast<unlocker_base*>(buffer);
ptr->unlock();
ptr->~unlocker_base();
}
private:
lock(const lock&);
lock& operator=(const lock&);
struct dummy_resource { void lock() {} void unlock() {} };
char buffer[sizeof(unlocker<dummy_resource>)];
};
class resource1 {
public:
void lock() {std::cout << "resource1::lock()\n";}
void unlock() {std::cout << "resource1::unlock()\n";}
};
class resource2 {
public:
void lock() {std::cout << "resource2::lock()\n";}
void unlock() {std::cout << "resource2::unlock()\n";}
};
int main()
{
{
resource1 res1;
resource2 res2;
{
lock l(res1);
}
{
lock l(res2);
}
}
return 0;
}
--
SpamTrap@gmx.de is never read
I'm Schobi at suespammers dot org
"The sarcasm is mightier than the sword."
Eric Jarvis
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]