This is what the constructor/destructor/move constructor of unique_lock
look like. The only reason for lock_guard's existence is to avoid the
possibility of the "empty" state. This avoids potential problems due to
not knowing the state of the lock, and avoids the check in the
destructor (which cannot always be optimized out).
> A make_lock_guard function doesn't even save you typing: your two lines
> above are the same length.
>
>
> It is not about line count :-)
>
> auto locked = std::make_lock_guard(mutex);
>
> is way better readable and behaves more nicely (refactoring?) than
>
> std::lock_guard<WhatMutexTypeWasItAgainHopeItWontChangeAnyTimeSoon>
> locked(mutex);
How about this then:
class generic_lock_guard{
struct base_unlocker{
virtual void unlock(void*)=0;
};
template<typename MutexType>
struct specific_unlocker:base_unlocker{
void unlock(void* m){
static_cast<MutexType*>(m)->unlock();
}
};
void* mutex;
base_unlocker& unlocker;
alignas(specific_unlocker<std::mutex>)
char buffer[sizeof(specific_unlocker<std::mutex>)];
generic_lock_guard(generic_lock_guard const&)=delete;
generic_lock_guard& operator=(generic_lock_guard const&)=delete;
public:
template<typename MutexType>
generic_lock_guard(MutexType & mutex_):
mutex(&mutex_),
unlocker(*(new(buffer) specific_unlocker<MutexType>))
{}
~generic_lock_guard()
{
unlocker.unlock(mutex);
}
};
Usage:
generic_lock_guard guard(some_mutex);
Doesn't matter what type some_mutex is, provided it has an unlock()
member. Assumption: all instances of specific_unlocker<T> have the same
size and alignment, which seems reasonable for an empty class.