Skip to content

Commit

Permalink
Add ExclusiveAccess, same as QMutexLocker but also provides data pointer
Browse files Browse the repository at this point in the history
  • Loading branch information
oblivioncth committed Nov 23, 2023
1 parent f27e1c7 commit fec8bba
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
102 changes: 102 additions & 0 deletions lib/core/include/qx/core/qx-exclusiveaccess.h
Original file line number Diff line number Diff line change
@@ -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<typename AccessType, typename Mutex>
requires any_of<Mutex, QMutex, QRecursiveMutex>
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
134 changes: 134 additions & 0 deletions lib/core/src/qx-exclusiveaccess.dox
Original file line number Diff line number Diff line change
@@ -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<Type, Mutex>::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<Type, Mutex>::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<Type, Mutex>::~ExclusiveAccess()
*
* Destroys the ExclusiveAccess and unlocks the mutex provided by the constructor if it's still locked.
*/

//-Class Functions----------------------------------------------------------------------------------------------
//Public:
/*!
* @fn void ExclusiveAccess<Type, Mutex>::isLocked() const
*
* Returns @c true if this ExclusiveAccess is currently locking its associated mutex; otherwise, returns
* @c false.
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::mutex() const
*
* Returns the mutex on which the ExclusiveAccess is operating.
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::relock()
*
* Relocks an unlocked ExclusiveAccess.
*
* @sa unlock().
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::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<Type, Mutex>::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<Type, Mutex>::access()
*
* Returns a pointer to the data the ExclusiveAccess is providing access to.
*/

/*!
* @fn const AccessType* ExclusiveAccess<Type, Mutex>::access() const
*
* @overload
*/

/*!
* @fn AccessType& ExclusiveAccess<Type, Mutex>::operator*()
*
* Returns a reference to the data the ExclusiveAccess is providing access to.
*/

/*!
* @fn const AccessType& ExclusiveAccess<Type, Mutex>::operator*()
*
* @overload
*/

/*!
* @fn AccessType* ExclusiveAccess<Type, Mutex>::operator->()
*
* Provides convenient access to the members of @a DataType for the accessible data.
*/

/*!
* @fn const AccessType* ExclusiveAccess<Type, Mutex>::operator->()
*
* @overload
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::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.
*/

0 comments on commit fec8bba

Please sign in to comment.