The namespace alib::singletons contains the concise and low-level module ALib Singletons. Although its scope is minimal, organizing the library orthogonally makes this separation beneficial.
This manual tries to tell you all about singletons and shows you alternative implementations. The use-case that class Singleton tackles are
This module contains a single class Singleton, which implements a singleton suitable for the use-case described above.
In C++, the commonly proposed solution using templates is straightforward and might not seem to warrant even a small module. This design pattern is called "Meyers’ Singleton" and is implemented as follows:
This approach generally works well. However, on Windows OS, if software accesses the same singleton object from different DLLs or from both a DLL and the main executable, each retrieves its own instance. Although running in one process, the singletons are "per DLL/Executable" because Windows DLLs have their own data segments for global variables. This contrasts with shared libraries on GNU/Linux or macOS, which behave like statically linked libraries.
The code in this repository creates true singleton objects, even when using WindowsOS DLLs. This is achieved by creating a static hash map that stores all singletons. The C++ runtime type information structure std::type_info
is used as the key for the singletons in the hash map.
Then, the type of the static local variable is turned into a pointer. If the pointer is not set, then the singleton is searched in the hash map, and if not found, created once.
This assures that only one singleton per process is created, regardless of how many times a DLL was loaded. From a performance perspective, the impact is minimal, because per DLL instance, only the first access to a singleton imposes an overhead. For the second, only an std::atomic
access to the already valid pointer is performed. On other systems, like GNU/Linux or macOS, the whole mechanism is omitted and the simple fallback variant that implements the design shown above is activated.
In case ALib is compiled in multithreaded mode, the creation and access of the singletons is threadsafe.
As mentioned above, this implementation is necessary only if a software process uses different data segments for different portions of the code, such as processes that load DLLs on Windows OS. Since this approach introduces a small overhead, the implementation can be switched to a simpler version that stores the singletons in a static field of the class. This behavior is controlled by the compiler-symbol ALIB_FEAT_SINGLETON_MAPPED, which defaults to true
on Windows OS and false
otherwise.
You might consider changing these defaults for the following reasons:
=0
to the compiler to switch to the simple mode.=1
to the compiler to enable the hash map, even though it's not necessary for that platform.The implementation details described above are transparent to the user when utilizing the Singleton class. A user-defined type can inherit the singleton functionality by deriving from Singleton<TDerivedClass>, where the template parameter is simply the name of the derived class. The static method Singleton::GetSingleton will then return a singleton instance of the derived type.
Here is a sample code deriving from this class:
Now, the singleton can be requested as follows:
Since Singleton::GetSingleton() is static, the call can be made without explicitly instantiating the class, ensuring a lazy initialization.
Note that the object returned by method Singleton::GetSingleton is not automatically the only instance of the custom class: While it can be considered to be "the" singleton instance, other instances might still be created. Usually, the term "strictness" is used to denote if a singleton type is to be allowed to have other instances of the type next to the singleton instance or not.
In this respected, types derived from class Singleton are not strict, as shown here:
To create classes that implement strict singletons, two things have to be done:
This is shown in the following sample:
Before terminating the process, the singletons collected in the global map, are to be destructed and deleted with namespace function singletons::shutdown, which is done with standard library shutdown
Especially note that the default ALib termination process described with module ALib Bootstrap, invokes this method already.
main()
. The reason for this is that the hashtable used to store singletons, is likewise a global object and might not be initialized at that point in time.As described above, to extend the singleton concept across DLL bounds, a static hash map is needed. Now, with debug-builds (see compiler-symbol ALIB_DEBUG), this map can be accessed with namespace function DbgGetSingletons.
If the ALib Build includes module ALib Strings, then an overloaded version exists with DbgGetSingletons(NAString&). This function performs a simple dump of all singleton types into the given AString.
The use-case definition was already given in the introduction. Here we want to give a real-world sample, taken from the module ALib Boxing. This module provides type Box that is a container for any sort of object. For example, the statement:
Box box= 42;
stores an integral value in a "box". The assignment operator (here constructor) stores a pointer to a singleton object along with the value. The singleton is of type VTable<T>, where T is the type that was boxed. In the case above VTable<int>. This singleton is used with any instance of class Box that holds an int value.
Now, if a custom code says:
MyType myObject; Box box= myObject;
a pointer to a singleton of type VTable<MyType> is needed. The use of class Singleton allows creating such singletons "on-the-fly" without the need of a corresponding global object definition.
This module should maybe not be used (to avoid unnecessary dependencies of your code to this library) in the following situations:
In these cases, alternatives may be:
It is possible to "optimize" the creation of singletons when using this module by providing specializations of dedicated "foreseeable" instantiations of the custom template type that is derived from class Singleton.
As a sample, we stick to the one presented in the section 9.1 Use-Cases. For all fundamental types and other important, aka "frequently boxed" types, the singleton mechanism of class Singleton is "disabled". Hence, if an integral is boxed:
Box box= 42;
the singleton of the VTable object is not created by method Singleton::GetSingleton. Only for "unknown" types this is done.
With that, not only the code size of the templated constructor of class Box shrinks, but it is allowed to create global objects of type Box that are initialized with values of such types.
In contrast to what was proposed above, namely by giving specializations of the templated VTable type, module ALib Boxing takes a slightly modified approach: The VTable singletons are exclusively received through the type trait VTableOptimizationTraits. Instead of specializing the VTable, this type is specialized for frequently used boxed types.
For further details on this implementation, consult chapter 12.2 Optimizations With Static VTables of the Programmer's Manual of module ALib Boxing and feel free to review the relevant source code found in boxing/detail/vtable.inl.