From 280815ff496790ec4ccdc8d3cf238848ba5b11d1 Mon Sep 17 00:00:00 2001 From: LoRd_MuldeR Date: Sat, 8 Dec 2018 15:17:33 +0100 Subject: [PATCH] Some improvements to Lazy class. Ensures that initializer is *not* called more than once. --- include/MUtils/Lazy.h | 49 ++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/include/MUtils/Lazy.h b/include/MUtils/Lazy.h index 267b751..ed21e2d 100644 --- a/include/MUtils/Lazy.h +++ b/include/MUtils/Lazy.h @@ -31,7 +31,9 @@ #include //Qt +#include #include +#include //CRT #include @@ -43,7 +45,7 @@ namespace MUtils * * The lazy-initialized value of type T can be obtained from a `Lazy` instance by using the `operator*()`. Initialization of the value happens when the `operator*()` is called for the very first time, by invoking the `initializer` lambda-function that was passed to the constructor. The return value of the `initializer` lambda-function is then stored internally, so that any subsequent call to the `operator*()` *immediately* returns the previously created value. * - * **Note on thread-saftey:** This class is thread-safe in the sense that all calls to `operator*()` on the same `Lazy` instance, regardless from which thread, are guaranteed to return the exactly same value/object. Still, if the value has *not* been initialized yet **and** if multiple threads happen to call `operator*()` at the same time, then the `initializer` lambda-function *may* be invoked more than once (concurrently and by different threads). In that case, all but one return value of the `initializer` lambda-function are discarded, and all threads eventually obtain the same value/object. + * **Note on thread-saftey:** This class is thread-safe in the sense that all calls to `operator*()` on the same `Lazy` instance, regardless from which thread, are guaranteed to return the exactly same value/object. The *first* thread trying to access the value will invoke the `initializer` lambda-function; concurrent threads may need to busy-wait until the initialization is completed. The `initializer` lambda-function is invoked at most once. */ template class Lazy { @@ -52,33 +54,46 @@ namespace MUtils T& operator*(void) { - T *value; - while (!(value = m_value)) - { - if (!(value = m_initializer())) - { - MUTILS_THROW("Initializer returned NULL pointer!"); - } - if (m_value.testAndSetOrdered(NULL, value)) - { - break; /*success*/ - } - delete value; - value = NULL; - } - return *value; + return (*getValue()); + } + + T* operator->(void) + { + return getValue(); } ~Lazy(void) { - if(T *const value = m_value.fetchAndStoreOrdered(NULL)) + if(T *const value = m_value) { delete value; } } + protected: + __forceinline T* getValue() + { + T *value; + while (!(value = m_value)) + { + if (m_status.testAndSetOrdered(0, 1)) + { + if (value = m_initializer()) + { + m_value.fetchAndStoreOrdered(value); + break; /*success*/ + } + m_status.fetchAndStoreOrdered(0); + MUTILS_THROW("Initializer returned NULL pointer!"); + } + QThread::yieldCurrentThread(); + } + return value; + } + private: QAtomicPointer m_value; + QAtomicInt m_status; const std::function m_initializer; }; }