Some improvements to Lazy<T> class. Ensures that initializer is *not* called more than once.

This commit is contained in:
LoRd_MuldeR 2018-12-08 15:17:33 +01:00
parent ec94dd9d6d
commit 280815ff49

View File

@ -31,7 +31,9 @@
#include <MUtils/Exception.h> #include <MUtils/Exception.h>
//Qt //Qt
#include <QThread>
#include <QAtomicPointer> #include <QAtomicPointer>
#include <QAtomicInt>
//CRT //CRT
#include <functional> #include <functional>
@ -43,7 +45,7 @@ namespace MUtils
* *
* The lazy-initialized value of type T can be obtained from a `Lazy<T>` 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. * The lazy-initialized value of type T can be obtained from a `Lazy<T>` 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<T>` 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<T>` 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<typename T> class Lazy template<typename T> class Lazy
{ {
@ -52,33 +54,46 @@ namespace MUtils
T& operator*(void) T& operator*(void)
{ {
T *value; return (*getValue());
while (!(value = m_value))
{
if (!(value = m_initializer()))
{
MUTILS_THROW("Initializer returned NULL pointer!");
} }
if (m_value.testAndSetOrdered(NULL, value))
T* operator->(void)
{ {
break; /*success*/ return getValue();
}
delete value;
value = NULL;
}
return *value;
} }
~Lazy(void) ~Lazy(void)
{ {
if(T *const value = m_value.fetchAndStoreOrdered(NULL)) if(T *const value = m_value)
{ {
delete 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: private:
QAtomicPointer<T> m_value; QAtomicPointer<T> m_value;
QAtomicInt m_status;
const std::function<T*(void)> m_initializer; const std::function<T*(void)> m_initializer;
}; };
} }