/** * @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 #include #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 class Safe { private: /// Type ValueType with reference removed, if present using RemoveRefValueType = typename std::remove_reference::type; /// Type MutexType with reference removed, if present using RemoveRefMutexType = typename std::remove_reference::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 class LockType, AccessMode Mode> class Access { // Make sure AccessMode is ReadOnly if a read-only lock is used static_assert(!(AccessTraits>::IsReadOnly && Mode==AccessMode::ReadWrite), "Cannot have ReadWrite access mode with ReadOnly lock. Check the value of AccessTraits::IsReadOnly if it exists."); /// ValueType with const qualifier if AccessMode is ReadOnly. using ConstIfReadOnlyValueType = typename std::conditional::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 EXPLICIT_IF_CPP17 Access(ReferenceType value, MutexType& mutex, OtherLockArgs&&... otherLockArgs): lock(mutex, std::forward(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 EXPLICIT_IF_CPP17 Access(const Safe& safe, OtherLockArgs&&... otherLockArgs): Access(safe.m_value, safe.m_mutex.get, std::forward(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 EXPLICIT_IF_CPP17 Access(Safe& safe, OtherLockArgs&&... otherLockArgs): Access(safe.m_value, safe.m_mutex.get, std::forward(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 class OtherLockType, AccessMode OtherMode, typename... OtherLockArgs> EXPLICIT_IF_CPP17 Access(Access& otherAccess, OtherLockArgs&&... otherLockArgs): Access(*otherAccess, *otherAccess.lock.release(), std::adopt_lock, std::forward(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 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 class LockType=DefaultReadOnlyLock> using ReadAccess = Access; template class LockType=DefaultReadWriteLock> using WriteAccess = Access; /** * @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 explicit Safe(DefaultConstructMutex, ValueArgs&&... valueArgs): m_mutex(), m_value(std::forward(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 explicit Safe(MutexArg&& mutexArg, ValueArgs&&... valueArgs): m_mutex{std::forward(mutexArg)}, m_value(std::forward(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 class LockType=DefaultReadOnlyLock, typename... LockArgs> ReadAccess readAccess(LockArgs&&... lockArgs) const { // using ReturnType = ReadAccess; return EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17{*this, std::forward(lockArgs)...}; } template class LockType=DefaultReadWriteLock, typename... LockArgs> WriteAccess writeAccess(LockArgs&&... lockArgs) { // using ReturnType = WriteAccess; return EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17{*this, std::forward(lockArgs)...}; } template class LockType=DefaultReadOnlyLock, typename... LockArgs> RemoveRefValueType copy(LockArgs&&... lockArgs) const { return *readAccess(std::forward(lockArgs)...); } template class LockType=DefaultReadWriteLock, typename... LockArgs> void assign(ConstValueReferenceType value, LockArgs&&... lockArgs) { *writeAccess(std::forward(lockArgs)...) = value; } template class LockType=DefaultReadWriteLock, typename... LockArgs> void assign(RemoveRefValueType&& value, LockArgs&&... lockArgs) { *writeAccess(std::forward(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 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 class LockType=DefaultReadOnlyLock> using ReadAccess = typename SafeType::template ReadAccess; /** * @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 class LockType=DefaultReadWriteLock> using WriteAccess = typename SafeType::template WriteAccess; } // namespace safe #undef EXPLICIT_IF_CPP17 #undef EXPLICITLY_CONSTRUCT_RETURN_TYPE_IF_CPP17