diff options
Diffstat (limited to '')
-rw-r--r-- | src/safe/safe.h | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/src/safe/safe.h b/src/safe/safe.h new file mode 100644 index 0000000..2beab55 --- /dev/null +++ b/src/safe/safe.h @@ -0,0 +1,359 @@ +/** + * @file safe.h + * @author L.-C. C. + * @brief + * @version 0.1 + * @date 2018-09-21 + * + * @copyright Copyright (c) 2018 + * + */ + +#pragma once + +#include "accessmode.h" +#include "defaulttypes.h" +#include "mutableref.h" + +#include <type_traits> +#include <utility> + +#if __cplusplus >= 201703L +#define EXPLICIT_IF_CPP17 explicit +#define EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17 ReturnType +#else +#define EXPLICIT_IF_CPP17 +#define EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17 +#endif + +namespace safe +{ + /** + * @brief Use this tag to default construct the mutex when constructing a + * Safe object. + */ + struct DefaultConstructMutex {}; + static constexpr DefaultConstructMutex default_construct_mutex; + + /** + * @brief Wraps a value together with a mutex. + * + * @tparam ValueType The type of the value to protect. + * @tparam MutexType The type of the mutex. + */ + template<typename ValueType, typename MutexType = DefaultMutex> + class Safe + { + private: + /// Type ValueType with reference removed, if present + using RemoveRefValueType = typename std::remove_reference<ValueType>::type; + /// Type MutexType with reference removed, if present + using RemoveRefMutexType = typename std::remove_reference<MutexType>::type; + + /** + * @brief Manages a mutex and gives pointer-like access to a value + * object. + * + * @tparam LockType The type of the lock object that manages the + * mutex, example: std::lock_guard. + * @tparam Mode Determines the access mode of the Access + * object. Can be either AccessMode::ReadOnly or + * AccessMode::ReadWrite. + */ + template<template<typename> class LockType, AccessMode Mode> + class Access + { + // Make sure AccessMode is ReadOnly if a read-only lock is used + static_assert(!(AccessTraits<LockType<RemoveRefMutexType>>::IsReadOnly && Mode==AccessMode::ReadWrite), "Cannot have ReadWrite access mode with ReadOnly lock. Check the value of AccessTraits<LockType>::IsReadOnly if it exists."); + + /// ValueType with const qualifier if AccessMode is ReadOnly. + using ConstIfReadOnlyValueType = typename std::conditional<Mode==AccessMode::ReadOnly, const RemoveRefValueType, RemoveRefValueType>::type; + + public: + /// Pointer-to-const ValueType + using ConstPointerType = const ConstIfReadOnlyValueType*; + /// Pointer-to-const ValueType if Mode is ReadOnly, pointer to ValueType otherwise. + using PointerType = ConstIfReadOnlyValueType*; + /// Reference-to-const ValueType + using ConstReferenceType = const ConstIfReadOnlyValueType&; + /// Reference-to-const ValueType if Mode is ReadOnly, reference to ValueType otherwise. + using ReferenceType = ConstIfReadOnlyValueType&; + + /** + * @brief Construct an Access object from a possibly const + * reference to the value object and any additionnal argument + * needed to construct the Lock object. + * + * @tparam LockArgs Deduced from lockArgs. + * @param value Reference to the value. + * @param lockArgs Arguments needed to construct the lock object. + */ + template<typename... OtherLockArgs> + EXPLICIT_IF_CPP17 + Access(ReferenceType value, MutexType& mutex, OtherLockArgs&&... otherLockArgs): + lock(mutex, std::forward<OtherLockArgs>(otherLockArgs)...), + m_value(value) + {} + + /** + * @brief Construct a read-only Access object from a const + * safe::Safe object and any additionnal argument needed to + * construct the Lock object. + * + * If needed, you can provide additionnal arguments to construct + * the lock object (such as std::adopt_lock). The mutex from the + * safe::Locakble object is already passed to the lock object's + * constructor though, you must not provide it. + * + * @tparam OtherLockArgs Deduced from otherLockArgs. + * @param safe The const Safe object to give protected access to. + * @param otherLockArgs Other arguments needed to construct the lock + * object. + */ + template<typename... OtherLockArgs> + EXPLICIT_IF_CPP17 + Access(const Safe& safe, OtherLockArgs&&... otherLockArgs): + Access(safe.m_value, safe.m_mutex.get, std::forward<OtherLockArgs>(otherLockArgs)...) + {} + + /** + * @brief Construct a read-write Access object from a + * safe::Safe object and any additionnal argument needed to + * construct the Lock object. + * + * If needed, you can provide additionnal arguments to construct + * the lock object (such as std::adopt_lock). The mutex from the + * safe object is already passed to the lock object's constructor + * though, you must not provide it. + * + * @tparam OtherLockArgs Deduced from otherLockArgs. + * @param safe The Safe object to give protected access to. + * @param otherLockArgs Other arguments needed to construct the lock + * object. + */ + template<typename... OtherLockArgs> + EXPLICIT_IF_CPP17 + Access(Safe& safe, OtherLockArgs&&... otherLockArgs): + Access(safe.m_value, safe.m_mutex.get, std::forward<OtherLockArgs>(otherLockArgs)...) + {} + + /** + * @brief Construct an Access object from another one. + * OtherLockType must implement release() like std::unique_lock + * does. + * + * @tparam OtherLockType Deduced from otherAccess. + * @tparam OtherMode Deduced from otherAccess. + * @tparam OtherLockArgs Deduced from otherLockArgs. + * @param otherAccess The Access object to construct from. + * @param otherLockArgs Other arguments needed to construct the lock + * object. + */ + template<template<typename> class OtherLockType, AccessMode OtherMode, typename... OtherLockArgs> + EXPLICIT_IF_CPP17 + Access(Access<OtherLockType, OtherMode>& otherAccess, OtherLockArgs&&... otherLockArgs): + Access(*otherAccess, *otherAccess.lock.release(), std::adopt_lock, std::forward<OtherLockArgs>(otherLockArgs)...) + { + static_assert(OtherMode == AccessMode::ReadWrite || OtherMode == Mode, "Cannot construct a ReadWrite Access object from a ReadOnly one!"); + } + + /** + * @brief Const accessor to the value. + * @return ConstPointerType Const pointer to the protected value. + */ + ConstPointerType operator->() const noexcept + { + return &m_value; + } + + /** + * @brief Accessor to the value. + * @return ValuePointerType Pointer to the protected value. + */ + PointerType operator->() noexcept + { + return &m_value; + } + + /** + * @brief Const accessor to the value. + * @return ConstValueReferenceType Const reference to the protected + * value. + */ + ConstReferenceType operator*() const noexcept + { + return m_value; + } + + /** + * @brief Accessor to the value. + * @return ValueReferenceType Reference to the protected. + */ + ReferenceType operator*() noexcept + { + return m_value; + } + + /// The lock that manages the mutex. + mutable LockType<RemoveRefMutexType> lock; + + private: + /// The protected value. + ReferenceType m_value; + }; + + /// Reference-to-const ValueType. + using ConstValueReferenceType = const RemoveRefValueType&; + /// Reference to ValueType. + using ValueReferenceType = RemoveRefValueType&; + /// Reference to MutexType. + using MutexReferenceType = RemoveRefMutexType&; + + public: + /// Aliases to ReadAccess and WriteAccess classes for this Safe class. + template<template<typename> class LockType=DefaultReadOnlyLock> + using ReadAccess = Access<LockType, AccessMode::ReadOnly>; + template<template<typename> class LockType=DefaultReadWriteLock> + using WriteAccess = Access<LockType, AccessMode::ReadWrite>; + + /** + * @brief Construct a Safe object + */ + Safe() = default; + + /** + * @brief Construct a Safe object with default construction of + * the mutex and perfect forwarding of the other arguments to + * construct the value object. + * + * @tparam ValueArgs Deduced from valueArgs. + * @param valueArgs Perfect forwarding arguments to construct the value object. + * @param tag Indicates that the mutex should be default constructed. + */ + template<typename... ValueArgs> + explicit Safe(DefaultConstructMutex, ValueArgs&&... valueArgs): + m_mutex(), + m_value(std::forward<ValueArgs>(valueArgs)...) + {} + /** + * @brief Construct a Safe object, forwarding the first + * argument to construct the mutex and the other arguments to + * construct the value object. + * + * @tparam MutexArg Deduced from mutexArg. + * @tparam ValueArgs Deduced from valueArgs. + * @param valueArgs Perfect forwarding arguments to construct the + * value object. + * @param mutexArg Perfect forwarding argument to construct the + * mutex object. + */ + template<typename MutexArg, typename... ValueArgs> + explicit Safe(MutexArg&& mutexArg, ValueArgs&&... valueArgs): + m_mutex{std::forward<MutexArg>(mutexArg)}, + m_value(std::forward<ValueArgs>(valueArgs)...) + {} + + /// Delete all copy/move construction/assignment, as these operations + /// require locking the mutex under the covers. + /// Use copy(), assign() and other defined constructors to get the behavior + /// you need with an explicit syntax. + Safe(const Safe&) = delete; + Safe(Safe&&) = delete; + Safe& operator =(const Safe&) = delete; + Safe& operator =(Safe&&) = delete; + + template<template<typename> class LockType=DefaultReadOnlyLock, typename... LockArgs> + ReadAccess<LockType> readAccess(LockArgs&&... lockArgs) const + { + // using ReturnType = ReadAccess<LockType>; + return EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17{*this, std::forward<LockArgs>(lockArgs)...}; + } + + template<template<typename> class LockType=DefaultReadWriteLock, typename... LockArgs> + WriteAccess<LockType> writeAccess(LockArgs&&... lockArgs) + { + // using ReturnType = WriteAccess<LockType>; + return EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17{*this, std::forward<LockArgs>(lockArgs)...}; + } + + template<template<typename> class LockType=DefaultReadOnlyLock, typename... LockArgs> + RemoveRefValueType copy(LockArgs&&... lockArgs) const + { + return *readAccess<LockType>(std::forward<LockArgs>(lockArgs)...); + } + + template<template<typename> class LockType=DefaultReadWriteLock, typename... LockArgs> + void assign(ConstValueReferenceType value, LockArgs&&... lockArgs) + { + *writeAccess<LockType>(std::forward<LockArgs>(lockArgs)...) = value; + } + template<template<typename> class LockType=DefaultReadWriteLock, typename... LockArgs> + void assign(RemoveRefValueType&& value, LockArgs&&... lockArgs) + { + *writeAccess<LockType>(std::forward<LockArgs>(lockArgs)...) = std::move(value); + } + + /** + * @brief Unsafe const accessor to the value. If you use this + * function, you exit the realm of safe! + * + * @return ConstValueReferenceType Const reference to the value + * object. + */ + ConstValueReferenceType unsafe() const noexcept + { + return m_value; + } + /** + * @brief Unsafe accessor to the value. If you use this function, + * you exit the realm of safe! + * + * @return ValueReferenceType Reference to the value object. + */ + ValueReferenceType unsafe() noexcept + { + return m_value; + } + + /** + * @brief Accessor to the mutex. + * + * @return MutexReferenceType Reference to the mutex. + */ + MutexReferenceType mutex() const noexcept + { + return m_mutex.get; + } + + private: + /// The helper object that holds the mutable mutex, or a reference to a mutex. + impl::MutableIfNotReference<MutexType> m_mutex; + /// The value to protect. + ValueType m_value; + }; + + /** + * @brief Type alias for read-only Access. + * + * @tparam SafeType The type of Safe object to give read-only access to. + * @tparam LockType=DefaultReadOnlyLock The type of lock. + */ + template< + typename SafeType, + template<typename> class LockType=DefaultReadOnlyLock> + using ReadAccess = typename SafeType::template ReadAccess<LockType>; + + /** + * @brief Type alias for read-write Access. + * + * @tparam SafeType The type of Safe object to give read-write access to. + * @tparam LockType=DefaultReadWriteLock The type of lock. + */ + template< + typename SafeType, + template<typename> class LockType=DefaultReadWriteLock> + using WriteAccess = typename SafeType::template WriteAccess<LockType>; +} // namespace safe + +#undef EXPLICIT_IF_CPP17 +#undef EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17 |