Static-Init: switch lib::Depend to embed the factory as Meyer's Singleton (#1142)
this is a (hopefully just temporary) workaround to deal with static initialisation ordering problems. The original solution was cleaner from a code readability viewpoint, however, when lib::Depend was used from static initialisation code, it could be observed that the factory constructor was invoked after first use. And while this did not interfer with the instance lifecycle management itself, because the zero-initialisation of the instance (atomic) pointer did happen beforehand, it would discard any special factory functions installed from such a context (and this counts as bug for my taste).
This commit is contained in:
parent
22b934673f
commit
852a3521db
4 changed files with 1196 additions and 893 deletions
|
|
@ -41,7 +41,11 @@
|
|||
|
||||
|
||||
/** @file try.cpp
|
||||
* Investigation: static initialisation order -- especially of static template member fields
|
||||
* Investigation: static initialisation order -- especially of static template member fields.
|
||||
* This version embeds the factory as Meyer's Singleton into the Front-Template using it, and
|
||||
* it invokes the factory from within the Front<TY> constructor -- thereby ensuring the factory
|
||||
* is indeed initialised prior to any usage and its lifespan extends beyond the lifespan of the
|
||||
* last instance using it.
|
||||
*/
|
||||
|
||||
typedef unsigned int uint;
|
||||
|
|
@ -76,25 +80,31 @@ template<typename T>
|
|||
class Front
|
||||
{
|
||||
public:
|
||||
static Factory<T> fac;
|
||||
Factory<T>&
|
||||
fac()
|
||||
{
|
||||
static Factory<T> fac;
|
||||
return fac;
|
||||
}
|
||||
|
||||
|
||||
Front()
|
||||
{
|
||||
cout << "Front-ctor val="<<fac.val<<endl;
|
||||
fac.val += 100;
|
||||
cout << "Front-ctor val="<<fac().val<<endl;
|
||||
fac().val += 100;
|
||||
}
|
||||
|
||||
T&
|
||||
operate ()
|
||||
{
|
||||
cout << "Front-operate val="<<fac.val<<endl;
|
||||
++ fac.val;
|
||||
return fac.val;
|
||||
cout << "Front-operate val="<<fac().val<<endl;
|
||||
++ fac().val;
|
||||
return fac().val;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
Factory<T> Front<T>::fac;
|
||||
//template<typename T>
|
||||
//Factory<T> Front<T>::fac;
|
||||
|
||||
|
||||
namespace {
|
||||
|
|
|
|||
|
|
@ -397,12 +397,12 @@ namespace lib {
|
|||
if (std::is_same<SRV,SUB>())
|
||||
{
|
||||
__ensure_pristine();
|
||||
Depend<SRV>::factory.defineCreatorAndManage (forward<FUN> (ctor));
|
||||
Depend<SRV>::factory().defineCreatorAndManage (forward<FUN> (ctor));
|
||||
}
|
||||
else
|
||||
{
|
||||
__ensure_pristine();
|
||||
Depend<SRV>::factory.defineCreator ([]{ return & Depend<SUB>{}(); });
|
||||
Depend<SRV>::factory().defineCreator ([]{ return & Depend<SUB>{}(); });
|
||||
DependInject<SUB>::useSingleton (forward<FUN> (ctor));
|
||||
} // delegate actual instance creation to Depend<SUB>
|
||||
}
|
||||
|
|
@ -415,7 +415,7 @@ namespace lib {
|
|||
{
|
||||
Lock guard;
|
||||
__ensure_pristine();
|
||||
Depend<SRV>::factory.defineCreator ([]{ return & Depend<SUB>{}(); });
|
||||
Depend<SRV>::factory().defineCreator ([]{ return & Depend<SUB>{}(); });
|
||||
}
|
||||
// note: we do not install an actual factory; rather we use the default for SUB
|
||||
}
|
||||
|
|
@ -426,9 +426,9 @@ namespace lib {
|
|||
temporarilyInstallAlternateFactory (SRV*& stashInstance, Factory& stashFac, FUN&& newFac)
|
||||
{
|
||||
Lock guard;
|
||||
stashFac.transferDefinition (move (Depend<SRV>::factory));
|
||||
stashFac.transferDefinition (move (Depend<SRV>::factory()));
|
||||
stashInstance = Depend<SRV>::instance;
|
||||
Depend<SRV>::factory.defineCreator (forward<FUN>(newFac));
|
||||
Depend<SRV>::factory().defineCreator (forward<FUN>(newFac));
|
||||
Depend<SRV>::instance = nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -436,7 +436,7 @@ namespace lib {
|
|||
restoreOriginalFactory (SRV*& stashInstance, Factory&& stashFac)
|
||||
{
|
||||
Lock guard;
|
||||
Depend<SRV>::factory.transferDefinition (move (stashFac));
|
||||
Depend<SRV>::factory().transferDefinition (move (stashFac));
|
||||
Depend<SRV>::instance = stashInstance;
|
||||
}
|
||||
|
||||
|
|
@ -449,7 +449,7 @@ namespace lib {
|
|||
"but another instance has already been dependency-injected."
|
||||
, error::LUMIERA_ERROR_LIFECYCLE);
|
||||
Depend<SRV>::instance = &newInstance;
|
||||
Depend<SRV>::factory.disable();
|
||||
Depend<SRV>::factory().disable();
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -457,7 +457,7 @@ namespace lib {
|
|||
{
|
||||
Lock guard;
|
||||
Depend<SRV>::instance = nullptr;
|
||||
Depend<SRV>::factory.disable();
|
||||
Depend<SRV>::factory().disable();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,20 @@
|
|||
** than the comparable unprotected direct access without lazy initialisation.
|
||||
** This is orders of magnitude better than any flavour of conventional locking.
|
||||
**
|
||||
** ### Initialisation of the shared factory
|
||||
**
|
||||
** We package the creation and destruction functors for the object to be managed
|
||||
** into a factory, which is shared per type. Such a shared factory could live within
|
||||
** a static member field `Depend<TY>::factory` -- however, the _definition_ of such
|
||||
** a templated static member happens on first use of the enclosing template _instance_,
|
||||
** and it seems the _initialisation order_ of such fields is not guaranteed, especially
|
||||
** when used prior to main, from static initialisation code. For that reason, we manage
|
||||
** the _factory_ as Meyer's singleton, so it can be accessed independently from the
|
||||
** actual target object's lifecycle and the compiler will ensure initialisation
|
||||
** prior to first use. To ensure the lifespan of this embedded factory object
|
||||
** extends beyond the last instance of `lib::Depend<TY>`, we also need to
|
||||
** access that factory from a ctor
|
||||
**
|
||||
** @see depend-inject.hpp
|
||||
** @see lib::DependInject
|
||||
** @see Singleton_test
|
||||
|
|
@ -137,7 +151,7 @@ namespace lib {
|
|||
}
|
||||
|
||||
OBJ*
|
||||
operator() ()
|
||||
buildTarget()
|
||||
{
|
||||
return creator_? creator_()
|
||||
: buildAndManage();
|
||||
|
|
@ -278,9 +292,15 @@ namespace lib {
|
|||
using Factory = DependencyFactory<SRV>;
|
||||
using Lock = ClassLock<SRV, NonrecursiveLock_NoWait>;
|
||||
|
||||
/* === shared per type === */
|
||||
/** shared per type */
|
||||
static Instance instance;
|
||||
static Factory factory;
|
||||
|
||||
static Factory&
|
||||
factory() ///< Meyer's Singleton to ensure initialisation on first use
|
||||
{
|
||||
static Factory sharedFactory;
|
||||
return sharedFactory;
|
||||
}
|
||||
|
||||
friend class DependInject<SRV>;
|
||||
|
||||
|
|
@ -298,15 +318,15 @@ namespace lib {
|
|||
SRV* object = instance.load (std::memory_order_acquire);
|
||||
if (!object)
|
||||
{
|
||||
factory.zombieCheck();
|
||||
factory().zombieCheck();
|
||||
Lock guard;
|
||||
|
||||
object = instance.load (std::memory_order_relaxed);
|
||||
if (!object)
|
||||
{
|
||||
object = factory();
|
||||
factory.disable();
|
||||
factory.atDestruction([]{ instance = nullptr; });
|
||||
object = factory().buildTarget();
|
||||
factory().disable();
|
||||
factory().atDestruction([]{ instance = nullptr; });
|
||||
}
|
||||
instance.store (object, std::memory_order_release);
|
||||
}
|
||||
|
|
@ -324,6 +344,14 @@ namespace lib {
|
|||
{
|
||||
return instance.load (std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/** @remark this ctor ensures the factory is created prior to first use,
|
||||
* and stays alive during the whole lifespan of any `Depend<TY>`
|
||||
*/
|
||||
Depend()
|
||||
{
|
||||
factory().zombieCheck();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -333,9 +361,6 @@ namespace lib {
|
|||
template<class SRV>
|
||||
std::atomic<SRV*> Depend<SRV>::instance;
|
||||
|
||||
template<class SRV>
|
||||
DependencyFactory<SRV> Depend<SRV>::factory;
|
||||
|
||||
|
||||
|
||||
} // namespace lib
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue