From fec8bbaa08e50623b84d774ef39162d848db33b4 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 23 Nov 2023 00:16:53 -0500 Subject: [PATCH] Add ExclusiveAccess, same as QMutexLocker but also provides data pointer --- lib/core/CMakeLists.txt | 2 + lib/core/include/qx/core/qx-exclusiveaccess.h | 102 +++++++++++++ lib/core/src/qx-exclusiveaccess.dox | 134 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 lib/core/include/qx/core/qx-exclusiveaccess.h create mode 100644 lib/core/src/qx-exclusiveaccess.dox diff --git a/lib/core/CMakeLists.txt b/lib/core/CMakeLists.txt index 86fa9721..883b07a8 100644 --- a/lib/core/CMakeLists.txt +++ b/lib/core/CMakeLists.txt @@ -15,6 +15,7 @@ qx_add_component("Core" qx-datetime.h qx-dsvtable.h qx-error.h + qx-exclusiveaccess.h qx-freeindextracker.h qx-genericerror.h qx-global.h @@ -67,6 +68,7 @@ qx_add_component("Core" DOC_ONLY qx-regularexpression.dox qx-bytearray.dox + qx-exclusiveaccess.dox qx-index.dox qx-iostream.dox qx-list.dox diff --git a/lib/core/include/qx/core/qx-exclusiveaccess.h b/lib/core/include/qx/core/qx-exclusiveaccess.h new file mode 100644 index 00000000..4655e1d4 --- /dev/null +++ b/lib/core/include/qx/core/qx-exclusiveaccess.h @@ -0,0 +1,102 @@ +#ifndef QX_EXCLUSIVE_ACCESS +#define QX_EXCLUSIVE_ACCESS + +// Extra-component Includes +#include "qx/utility/qx-concepts.h" + +class QMutex; +class QRecursiveMutex; + +namespace Qx +{ + +template + requires any_of +class ExclusiveAccess +{ +//-Instance Variables--------------------------------------------------------------------------------------- +private: + AccessType* mAccess; + Mutex* mMutex; + bool mLocked; + +//-Constructor---------------------------------------------------------------------------------------------- +private: + ExclusiveAccess(const ExclusiveAccess&) = delete; + +public: + [[nodiscard]] explicit ExclusiveAccess(AccessType* data, Mutex* mutex) noexcept: + mLocked(false) + { + mAccess = data; + mMutex = mutex; + if(mMutex) [[likely]] + { + mMutex->lock(); + mLocked = true; + } + } + + [[nodiscard]] ExclusiveAccess(ExclusiveAccess&& other) noexcept : + mAccess(std::exchange(other.mAccess, nullptr)), + mMutex(std::exchange(other.mMutex, nullptr)), + mLocked(std::exchange(other.mLocked, false)) + {} + +//-Destructor---------------------------------------------------------------------------------------------- +public: + ~ExclusiveAccess() + { + if(mLocked) + unlock(); + } + +//-Instance Functions---------------------------------------------------------------------------------------------- +public: + bool isLocked() const noexcept { return mLocked; } + + void unlock() noexcept + { + Q_ASSERT(mLocked); + mMutex->unlock(); + mLocked = false; + } + + void relock() noexcept + { + Q_ASSERT(!mLocked); + mMutex->lock(); + mLocked = true; + } + + void swap(ExclusiveAccess& other) noexcept + { + using std::swap; // Allows use of specialized swap method for members, if they exist + swap(mAccess, other.mAccess); + swap(mMutex, other.mMutex); + swap(mLocked, other.mLocked); + } + + Mutex* mutex() const { return mMutex; } + + AccessType* access() { return mAccess; } + const AccessType* access() const { return mAccess; } + + AccessType& operator*() { Q_ASSERT(mAccess); return *mAccess; } + const AccessType& operator*() const { Q_ASSERT(mAccess); return *mAccess; } + + AccessType* operator->() { Q_ASSERT(mAccess); return mAccess; } + const AccessType* operator->() const { Q_ASSERT(mAccess); return mAccess; } + + // Move-and-swap, which insures other's data is fully cleared (i.e. it loses exclusive access) due to the move construct + ExclusiveAccess &operator=(ExclusiveAccess&& other) noexcept + { + ExclusiveAccess moved(std::move(other)); + swap(other); + return *this; + } +}; + +} + +#endif // QX_EXCLUSIVE_ACCESS diff --git a/lib/core/src/qx-exclusiveaccess.dox b/lib/core/src/qx-exclusiveaccess.dox new file mode 100644 index 00000000..3679cfa3 --- /dev/null +++ b/lib/core/src/qx-exclusiveaccess.dox @@ -0,0 +1,134 @@ +namespace Qx +{ +//=============================================================================================================== +// ExclusiveAccess +//=============================================================================================================== + +/*! + * @class ExclusiveAccess qx/core/qx-exclusiveaccess.h + * @ingroup qx-core + * + * @brief The ExclusiveAccess template class is a convenience class that simplifies access to resources + * secured by a mutex. + * + * This class is essentially a slightly more capable QMutexLocker and can be used in the same ways; however, + * it couples access to the data protected by the mutex (in cases where there is a single resource) along with + * the automatic locking/unlocking of QMutexLocker. + * + * This can be useful when exclusive access to data needs to be provided by a class/function: + * + * @snippet qx-exclusiveaccess.cpp 0 + */ + +//-Constructor---------------------------------------------------------------------------------------------- +//Public: +/*! + * @fn ExclusiveAccess::ExclusiveAccess(AccessType* data, Mutex* mutex) + * + * Constructs an ExclusiveAccess and locks @a mutex. The mutex will be unlocked when the ExclusiveAccess is + * destroyed. If @a mutex is @c nullptr, ExclusiveAccess only provides access to @a data. + * + * @sa QMutexLocker. + */ + +/*! + * @fn ExclusiveAccess::ExclusiveAccess(ExclusiveAccess&& other) + * + * Move-constructs and ExclusiveAccess from @a other. The mutex, data pointer, and state of @a other is + * transferred to the newly constructed instance. After the move, @a other will no longer manage the mutex, + * nor have a valid data pointer. + * + * @sa QMutexLocker. + */ + +//-Destructor------------------------------------------------------------------------------------------------ +//Public: +/*! + * @fn ExclusiveAccess::~ExclusiveAccess() + * + * Destroys the ExclusiveAccess and unlocks the mutex provided by the constructor if it's still locked. + */ + +//-Class Functions---------------------------------------------------------------------------------------------- +//Public: +/*! + * @fn void ExclusiveAccess::isLocked() const + * + * Returns @c true if this ExclusiveAccess is currently locking its associated mutex; otherwise, returns + * @c false. + */ + +/*! + * @fn void ExclusiveAccess::mutex() const + * + * Returns the mutex on which the ExclusiveAccess is operating. + */ + +/*! + * @fn void ExclusiveAccess::relock() + * + * Relocks an unlocked ExclusiveAccess. + * + * @sa unlock(). + */ + +/*! + * @fn void ExclusiveAccess::swap(ExclusiveAccess& other) + * + * Swaps the mutex, data pointer, and state of this ExclusiveAccess with @other. This operation + * is very fast and never fails. + */ + +/*! +* @fn void ExclusiveAccess::unlock() +* +* Unlocks this ExclusiveAccess. You can use relock() to lock it again. It does not need to be +* locked when destroyed. +* +* @sa relock(). +*/ + +/*! +* @fn AccessType* ExclusiveAccess::access() +* +* Returns a pointer to the data the ExclusiveAccess is providing access to. +*/ + +/*! +* @fn const AccessType* ExclusiveAccess::access() const +* +* @overload +*/ + +/*! +* @fn AccessType& ExclusiveAccess::operator*() +* +* Returns a reference to the data the ExclusiveAccess is providing access to. +*/ + +/*! +* @fn const AccessType& ExclusiveAccess::operator*() +* +* @overload +*/ + +/*! +* @fn AccessType* ExclusiveAccess::operator->() +* +* Provides convenient access to the members of @a DataType for the accessible data. +*/ + +/*! +* @fn const AccessType* ExclusiveAccess::operator->() +* +* @overload +*/ + +/*! +* @fn void ExclusiveAccess::operator=(ExclusiveAccess&& other) +* +* Move-assigns @a other onto this ExclusiveAccess. If this ExclusiveAccess was holding onto a +* locked mutex before the assignment, the mutex will be unlocked. The mutex, data pointer, and +* state of @a other is then transferred to this QMutexLocker. After the move, @a other will +* no longer manage the mutex, nor have a valid data pointer. +*/