lnav/src/safe/safe.h

360 lines
12 KiB
C++

/**
* @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