1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
| #ifndef INCLUDE__PYRAMIDEV__THREAD_SHARED__H
#define INCLUDE__PYRAMIDEV__THREAD_SHARED__H
/*!
* \file ThreadShared.h
* \brief Définit le modèle de classe ThreadShared qui aide à n'utiliser un objet que quand son mutex associé est bloqué.
* \details Contexte :\n
* Quand on a une référence vers un objet, comment savoir si l'accès à cet objet est déjà protégé ?\n
* Une première solution est de documenter ou de choisir les bons noms de variable :\n
\code
void toto(Bar& bar_a_proteger, const Foo& foo_deja_protege)
{
// Chercher le mutex associé à bar_a_proteger et le bloquer.
// code...
}
\endcode
* Le problème, c'est que cela demande de la rigueur et que ce n'est pas contrôlé à la compilation.\n
* Une autre solution consiste à mettre systématiquement ensemble l'objet et la référence vers
* le mutex pour signaler qu'il faut protéger l'objet :\n
\code
void toto(std::pair<Bar, std::mutex&> bar, const Foo& foo)
{
std::lock_guard<std::mutex> lock(bar.second);
// foo n'est pas accompagné d'un mutex ici, donc on considère qu'il est déjà protégé,
// ou bien qu'il n'est pas partagé par plusieurs processus.
// code...
}
\endcode
* La solution avec ThreadShared est similaire sauf que, à la place de
* std::pair<Bar, std::mutex&>, on utilise ThreadShared<Bar, std::mutex>
* dont l'interface oblige à bloquer le mutex pour accéder à l'objet et force le déblocage
* du mutex quand on sort du bloc où on a bloqué le mutex
* (sauf bidouille intentionnelle de l'utilisateur avec la sémantique de mouvement).
\code
void toto(pyramidev::ThreadShared<Bar, std::mutex>& bar, const Foo& foo)
{
std::pair<Bar&, pyramidev::DestructorWillUnlock<std::mutex>> access = bar.scopeAccess();
Bar& barRef = access.first; // en attendant les structured bindings de C++17
// foo n'est pas dans un ThreadShared, donc on considère qu'il est déjà protégé,
// ou bien qu'il n'est pas partagé par plusieurs processus.
// code
}
void titi(const pyramidev::ThreadShared<Foo, std::shared_mutex>& foo)
{
std::pair<const Foo&, pyramidev::DestructorWillUnlockShared<std::shared_mutex>>
access = foo.scopeAccessShared();
const Foo& fooRef = access.first; // en attendant les structured bindings de C++17
// code
}
\endcode
*/
#include "StdMutexAdapter.h"
#include "DestructorWillUnlock.h"
namespace pyramidev {
/*!
* \brief Classe qui aide à n'utiliser un objet que quand son mutex associé est bloqué.
* \warning Si Mutex n'a pas la même syntaxe que les mutex de la STL et si MutexWrapper est
* StdMutexAdapter<Mutex>, alors il faut spécialiser le modèle StdMutexAdapter pour
* le type Mutex. Sinon, ça ne compilera pas.
* \warning "Mutex&" doit être implicitement convertible en "MutexWrapper::mutex_type&".
*/
template<typename T, class Mutex, class MutexWrapper = StdMutexAdapter<Mutex>>
class ThreadShared
{
ThreadShared(const ThreadShared&) = delete;
ThreadShared(ThreadShared&&) = delete;
ThreadShared& operator=(const ThreadShared&) = delete;
ThreadShared& operator=(ThreadShared&&) = delete;
private:
T m_obj;
mutable MutexWrapper m_mtx;
public:
template<typename... Args>
explicit ThreadShared(Mutex& mtx, Args&&... args) :
m_obj(std::forward<Args>(args)...),
m_mtx(mtx)
{}
~ThreadShared() {}
std::pair<T&, DestructorWillUnlockStdMutex<MutexWrapper>> scopeAccess()
{
m_mtx.lock();
return std::pair<T&, DestructorWillUnlockStdMutex<MutexWrapper>>(
m_obj, DestructorWillUnlockStdMutex<MutexWrapper>(&m_mtx));
}
std::pair<const T&, DestructorWillUnlockStdMutex<MutexWrapper>> scopeAccess() const
{
m_mtx.lock();
return std::pair<const T&, DestructorWillUnlockStdMutex<MutexWrapper>>(
m_obj, DestructorWillUnlockStdMutex<MutexWrapper>(&m_mtx));
}
std::pair<const T&, DestructorWillUnlockSharedStdMutex<MutexWrapper>> scopeAccessShared() const
{
m_mtx.lock_shared();
return std::pair<const T&, DestructorWillUnlockSharedStdMutex<MutexWrapper>>(
m_obj, DestructorWillUnlockSharedStdMutex<MutexWrapper>(&m_mtx));
}
/*!
* \details Si le blocage du mutex échoue, on retourne un pointeur nul et
* un objet dont le destructeur ne fera rien.
*/
std::pair<T*, DestructorWillUnlockStdMutex<MutexWrapper>> tryScopeAccess()
{
const bool locked = m_mtx.try_lock();
T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<T*, DestructorWillUnlockStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockStdMutex<MutexWrapper>(mtxPtr));
}
std::pair<const T*, DestructorWillUnlockStdMutex<MutexWrapper>> tryScopeAccess() const
{
const bool locked = m_mtx.try_lock();
const T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<const T*, DestructorWillUnlockStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockStdMutex<MutexWrapper>(mtxPtr));
}
std::pair<const T*, DestructorWillUnlockSharedStdMutex<MutexWrapper>> tryScopeAccessShared() const
{
const bool locked = m_mtx.try_lock_shared();
const T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<const T*, DestructorWillUnlockSharedStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockSharedStdMutex<MutexWrapper>(mtxPtr));
}
template<class Rep, class Period>
std::pair<T*, DestructorWillUnlockStdMutex<MutexWrapper>>
tryScopeAccessFor(const std::chrono::duration<Rep,Period>& timeout_duration)
{
const bool locked = m_mtx.try_lock_for(timeout_duration);
T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<T*, DestructorWillUnlockStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockStdMutex<MutexWrapper>(mtxPtr));
}
template<class Rep, class Period>
std::pair<const T*, DestructorWillUnlockStdMutex<MutexWrapper>>
tryScopeAccessFor(const std::chrono::duration<Rep,Period>& timeout_duration) const
{
const bool locked = m_mtx.try_lock_for(timeout_duration);
const T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<const T*, DestructorWillUnlockStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockStdMutex<MutexWrapper>(mtxPtr));
}
template<class Rep, class Period>
std::pair<const T*, DestructorWillUnlockSharedStdMutex<MutexWrapper>>
tryScopeAccessSharedFor(const std::chrono::duration<Rep,Period>& timeout_duration) const
{
const bool locked = m_mtx.try_lock_shared_for(timeout_duration);
const T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<const T*, DestructorWillUnlockSharedStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockSharedStdMutex<MutexWrapper>(mtxPtr));
}
template<class Clock, class Duration>
std::pair<T*, DestructorWillUnlockStdMutex<MutexWrapper>>
tryScopeAccessUntil(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
const bool locked = m_mtx.try_lock_until(timeout_time);
T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<T*, DestructorWillUnlockStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockStdMutex<MutexWrapper>(mtxPtr));
}
template<class Clock, class Duration>
std::pair<const T*, DestructorWillUnlockStdMutex<MutexWrapper>>
tryScopeAccessUntil(const std::chrono::time_point<Clock,Duration>& timeout_time) const
{
const bool locked = m_mtx.try_lock_until(timeout_time);
const T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<const T*, DestructorWillUnlockStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockStdMutex<MutexWrapper>(mtxPtr));
}
template<class Clock, class Duration>
std::pair<const T*, DestructorWillUnlockSharedStdMutex<MutexWrapper>>
tryScopeAccessSharedUntil(const std::chrono::time_point<Clock,Duration>& timeout_time) const
{
const bool locked = m_mtx.try_lock_shared_until(timeout_time);
const T* const objPtr = locked ? &m_obj : nullptr;
MutexWrapper* const mtxPtr = locked ? &m_mtx : nullptr;
return std::pair<const T*, DestructorWillUnlockSharedStdMutex<MutexWrapper>>(
objPtr, DestructorWillUnlockSharedStdMutex<MutexWrapper>(mtxPtr));
}
};
} // namespace pyramidev
#endif |