blob: f39b2d0e5ffc713c60f226e9f287a0bb3beb62f1 (
plain) (
tree)
|
|
#ifndef UTILS_OWNING_MUTEX_H
#define UTILS_OWNING_MUTEX_H
#include <mutex>
#include <cassert>
template <typename T, typename M>
struct guard;
// -- OWNING MUTEX -------------------------------------------------------------
/// owning_mutex
///
/// An mutex wrapper type that owns a value of type T and provides mutual
/// exclusive access to that value through guard objects. Guard objects are
/// obtained from the wrapper API. When a guard goes out of scope, the mutex
/// will be unlocked automatically.
///
/// The mutex type can be controlled by the template type argument M.
/// M: BasicLockable
///
/// EXAMPLE:
/// struct data { int a; };
/// owning_mutex<data> val{1};
///
/// {
/// auto guard = val.lock();
/// guard->a = 1337;
/// // mutex will be unlocked after this scope
/// }
template <typename T, typename M = std::mutex>
struct owning_mutex {
template <typename... Args>
constexpr explicit owning_mutex(Args... args)
: m_val{std::forward<Args>(args)...} {}
owning_mutex(const owning_mutex&) = delete;
owning_mutex(owning_mutex&&) = delete;
guard<T, M> lock() {
return {m_mtx, m_val};
}
private:
M m_mtx;
T m_val;
};
// -- GUARD --------------------------------------------------------------------
#if __cplusplus >= 201703L
template <typename T, typename M>
struct [[nodiscard]] guard {
guard(M& mtx, T& val) : m_lk{mtx}, m_val{val} {}
// With the guaranteed copy elision (cpp17) we can truly delete the
// copy/move constructor of the guard type.
//
// https://stackoverflow.com/a/38043447
guard(const guard&) = delete;
guard(guard&&) = delete;
T& operator*() {
return m_val;
}
T* operator->() {
return &m_val;
}
private:
std::lock_guard<M> m_lk;
T& m_val;
};
#else // before cpp17
template <typename T, typename M>
struct guard {
guard(M& mtx, T& val) : m_mtx{&mtx}, m_val{val} {
m_mtx->lock();
}
~guard() {
if (m_mtx) {
m_mtx->unlock();
}
}
T& operator*() {
assert(m_mtx != nullptr);
return m_val;
}
T* operator->() {
if (!m_mtx) {
return nullptr;
}
return &m_val;
}
guard(const guard&) = delete;
// Implement move constructor for cases where the compiler does no copy
// elision.
// For API compatibility with the cpp17 version, the move constructor
// should not be explicitly invoked by the user.
//
// SAFETY: Exclusive access to T is guaranteed as at any given time only a
// single *guard* instance is NOT moved.
//
// UB: The *guard* must not be moved across thread boundaries and dropped
// there.
guard(guard&& rhs) noexcept : m_mtx{rhs.m_mtx}, m_val{rhs.m_val} {
rhs.m_mtx = nullptr;
}
private:
M* m_mtx;
T& m_val;
};
#endif
#endif
|