ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
ALib Module Singleton - Programmer's Manual

1. Introduction

This namespace is the home of the very tiny and low-level ALib Module "Singleton". While the scope is almost too small to be named a module, for organizing the library as "orthogonal" as possible, the separation is helpful. And, should a programmer be really not interested in anything else of ALib than the singleton feature, this module is well extractable from the rest of the library and only a few files remain with no "clutter" around class Singleton .

It could be said, that this manual is written in reverse order. The use case of the library and its alternatives is only given in the last chapter. The reason for this is that presenting the solution first, just makes it more easy to understand when this library is applicable.
Consequently, experienced programmers might want to read sections 9.1 Use-Cases and 9.2 Alternatives first!

2. C++ Singletons

The only class found in this module is Singleton , which implements the well known Singleton Design Pattern. With C++ templates, the usually proposed solution is so simple, that it would not justify even this tiny module. Such simple solution looks as follows:

// template class to provide singleton functionality
template<typename T> class Singleton
{
public: static T* get()
{
static T thesingleton;
return &thesingleton;
}
};
// usage:
class Sample : public Singleton<Sample>
{
// implementation of sample class...
};
Sample* sample= Sample::get();
singletons::Singleton< T > Singleton
Type alias in namespace alib.

This works fine in general unless a software running on Windows OS, starts to access the same singleton object from within different Windows DLLs or a Windows DLL and the main executable. Although running in one process, the singletons retrieved are singletons "per DLL/Executable". The reason for this is simply spoken that Windows DLLs dispose of an own data segment for global variables.
This is different with shared libraries under GNU/Linux or macOS, which are designed to behave exactly as statically linked libraries.

The code provided with this repository creates true singleton objects, even when Windows OS DLLs are used. This is achieved by creating a static hash map (only once) and collecting all singletons herein. The C++ runtime type information struct std::type_info is used as the key to the singletons in the hash map.

3. Switching Compilation Mode

As said above, the effort done with this implementation is needed only if a software process uses different data segments for different portions of the code, for example processes which open DLLs on Windows OS. As this effort imposes a (small) overhead, the implementation of this class can be switched to a simple implementation version, which just stores the singletons in a static field of the class. The implementation is controlled by compiler symbol ALIB_FEAT_SINGLETON_MAPPED. On Windows OS the symbol defaults to true, otherwise to false.

Changing these defaults could be done for the following reasons:

  • ALib is used on Windows OS, but not compiled as a DLL and the code that is using ALib is restricted to one DLL or the main application exclusively. In this case, symbol ALIB_FEAT_SINGLETON_MAPPED=0 may be passed to the compiler to switch compilation to the simple mode.
  • ALib is used on GNU/Linux, but the debug features for listing all singletons created (described below) should be used. In this case, for the purpose of debugging, ALIB_FEAT_SINGLETON_MAPPED=1 may be passed to the compiler to switch compilation to use the hash map, even though this is otherwise not needed for that platform.

4. Usage

The implementation details described above are transparent in respect to using the class.

A user type may inherit the singleton feature from this class by being derived from it, with the template parameter TDerivedClass set to name just of the derived class type itself. Then, static method Singleton::GetSingleton will return a singleton of the derived type.

Here is a sample code deriving from this class:

// Derive a class from singleton, providing its name as template parameter:
class MyClass : public alib::Singleton<MyClass>
{
//... MyClass implementation
};

Now, the singleton can be requested as follows:

// Then, the singleton can be received as:
MyClass& myClassSingleton= MyClass::GetSingleton();
std::cout << "The singleton of MyClass is: " << std::hex << &myClassSingleton << std::endl;

5. Strict Singletons

Note that the object returned by method Singleton::GetSingleton is not automatically the only instance of the custom class: While it is "the singleton" object, 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:

MyClass instance2;
std::cout << "Another instance of MyClass is: " << std::hex << &instance2 << std::endl;

To create classes that implement strict singletons, two things have to be done:

  1. Provide a private constructor to the derived class and
  2. make parent class Singleton be a friend of it.

This is shown in the following sample:

// Derive a class from singleton, providing its name as template parameter:
class JustOne : public alib::Singleton<JustOne>
{
private: JustOne() {}
//... class JustOne implementation
};
JustOne& theOne= JustOne::GetSingleton();
JustOne theSecond; // Compiler error, not allowed!

6. Memory Cleanup

Prior to terminating the process, the singletons collected in the global map, can be destructed and deleted using namespace function Shutdown . See this method's description for more information.

Especially note that default module termination (namely invoking function Shutdown invokes this method already.

7. Restrictions / Penalties

  • The singleton class needs to have a default (parameterless) constructor. (This might be overcome with a templated get-method that forwards variadic arguments, but is not implemented here.)
  • Singletons must not be received by constructor code of global or static objects, aka code that is executed prior to entering method 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 yet.
  • The singleton class is virtual and hence all derived classes become virtual. This is needed to be able to have a virtual destructor.
  • There is a minimal performance penalty in comparison to more simple implementations of the singleton design pattern: Each 'code entity' (DLL or main executable) needs to retrieve a singleton which was potentially already created by another code entity, once. In other words, only the first request for the singleton incorporates two small performance penalties for
    1. setting and releasing a thread lock (mutex)
    2. retrieving the singleton or inserting the singleton if not retrieved.
  • The memory penalty is also quite minimal and imposed by the creation of a static hash table, which holds a pointer to each Singleton.
  • Upon exit of the process, programmers might want to explicitly free the hash table to avoid the detection of memory leaks by metrics tools like Valgrind . (Otherwise this can be omitted, as the memory is cleaned by the OS probably much faster when a process exits).
  • In general, it is advised to think twice before implementing the singleton design pattern. Often, this is considered bad code design. However, there can be very good reasons for having singletons in software. And do not be frustrated if "very wise people" tell you that you must never use a singleton. Your answer to them is: Each and every created instance of a virtual C++ class contains a pointer to a singleton, namely the vtable !

8. Listing Types in Debug Compilations

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 Distribution 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.

9. Use-Cases, Alternatives, Optimization

9.1 Use-Cases

With the explanations given in the previous chapters, the precise use-case for this library can be defined:

ALib Singletons is to be used when implicit definitions of singleton objects of templated types are needed.

In other words: The ultimate goal of this module was to allow a library to present a templated singleton type to a using code. When the using code created a custom instantiation of the type, no need for a parallel definition of a global singleton instance for this template instantiation should be needed.

This might be explained with a quick sample. Module ALib Boxing provides type Box that is a container for any sort of object. For example:

    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 to create such singletons "on-the-fly" without the need of a corresponding global object definition.

9.2 Alternatives

This module should maybe not be used (to avoid unnecessary dependencies of your code to this library) in the following situations:

  • If no templated singletons are needed. In this case, a virtual class should be preferred that provides an abstract virtual method Get which has to be overloaded by each derived class.
  • If templated singletons are needed, but the set of template instantiations of that singleton type is known upfront - or it is no problem to demand a definition of a singleton object for each new instantiation performed.
  • If a software is not planned to be compiled for Windows OS, or only for monolithic (non-DLL) executables. In this case the simple alternative is to use a code similar to the one sampled above.

9.3 Optimization With Mixed Approach

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 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;

then 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.

Note
Due to the potential hashtable access of method Singleton::GetSingleton, the initialization of a global instance of class Box is otherwise undefined behavior, as it is not guaranteed that the likewise global hashmap is yet initialized at this point in time!

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 a next template type T_VTableFactory 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 relevant source code found in alib/boxing/detail/vtable.inl .