#ifndef UTILS_MUTEX_H #define UTILS_MUTEX_H #include #include template 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 val{1}; /// /// { /// auto guard = val.lock(); /// guard->a = 1337; /// // mutex will be unlocked after this scope /// } template struct owning_mutex { template constexpr explicit owning_mutex(Args... args) : m_val{std::forward(args)...} {} owning_mutex(const owning_mutex&) = delete; owning_mutex(owning_mutex&&) = delete; guard lock() { return {m_mtx, m_val}; } private: M m_mtx; T m_val; }; // -- GUARD -------------------------------------------------------------------- #if __cplusplus >= 201703L template 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_lk; T& m_val; }; #else // before cpp17 template 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