TSDuck Version 3.15-964 (TSDuck - The MPEG Transport Stream Toolkit)
tsStaticInstance.h File Reference

Declare the initialization-order-safe macros for static object instances. More...

Macros

#define TS_STATIC_INSTANCE(ObjectClass, ObjectArgs, StaticInstanceClass)
 Local declaration of a static object regardless of modules initialization order. More...
 
#define TS_STATIC_INSTANCE_DECLARATION(ObjectClass, ClassAttributes, StaticInstanceClass)
 Declaration of a static object regardless of modules initialization order. More...
 
#define TS_STATIC_INSTANCE_DEFINITION(ObjectClass, ObjectArgs, StaticInstancePath, StaticInstanceClass)
 Definition of a static object regardless of modules initialization order. More...
 

Detailed Description

Declare the initialization-order-safe macros for static object instances.

Macro Definition Documentation

◆ TS_STATIC_INSTANCE_DECLARATION

#define TS_STATIC_INSTANCE_DECLARATION (   ObjectClass,
  ClassAttributes,
  StaticInstanceClass 
)

Declaration of a static object regardless of modules initialization order.

This macro expands to the declaration part of the static instance. See TS_STATIC_INSTANCE for more details on the usage of static instances.

The macro TS_STATIC_INSTANCE_DECLARATION is used in cunjunction with its counterpart TS_STATIC_INSTANCE_DEFINITION. They are used when the static instance must be publicly accessible or declared in a specific namespace.

Most of the time, a static instance is used privately inside a module and declared in the anonymous namespace. In this general case, the macro TS_STATIC_INSTANCE is a simpler alternative.

In the unusual case where you need to make a static instance publicly available, use the macro TS_STATIC_INSTANCE_DECLARATION in the header file and the macro TS_STATIC_INSTANCE_DEFINITION in the C++ body file as illustrated below.

In the header file (.h) of the module, we declare a static instance of std::string in the namespace ts::foo.

namespace ts {
namespace foo {
}
}

In the definition body file (.cpp) of the module:

TS_STATIC_INSTANCE_DEFINITION(std::string, ("initial value"), ts::foo::Bar, Bar);

In application code, we use the static instance of string as follow:

std::cout << "static string instance: " << ts::foo::Bar::Instance() << std::endl;
Parameters
ObjectClassThe name of the class of the static object. This must be an existing class.
ClassAttributesAttributes of StaticInstanceClass, typically TSDUCKDLL or empty.
StaticInstanceClassThe name of the new class which encapsulates the static instance. This class is declared by the expansion of the macro.
See also
TS_STATIC_INSTANCE for more details on the usage of static instances.
TS_STATIC_INSTANCE_DEFINITION for the definition part.

◆ TS_STATIC_INSTANCE_DEFINITION

#define TS_STATIC_INSTANCE_DEFINITION (   ObjectClass,
  ObjectArgs,
  StaticInstancePath,
  StaticInstanceClass 
)

Definition of a static object regardless of modules initialization order.

This macro expands to the definition part of the static instance.

Parameters
ObjectClassThe name of the class of the static object. This must be an existing class.
ObjectArgsThe initializer list, enclosed within parentheses, of the constructor of the static instance. Use () if there is no initializer.
StaticInstancePathThe full name, including namespaces if any, of the new class which encapsulates the static instance. This class is defined by the expansion of the macro.
StaticInstanceClassThe simple name, without namespace, of the new class which encapsulates the static instance. This is the class name part of StaticInstancePath. If there is no namespace, StaticInstancePath and StaticInstanceClass are identical.
See also
TS_STATIC_INSTANCE for more details on the usage of static instances.
TS_STATIC_INSTANCE_DECLARATION for the declaration part and usage examples.

◆ TS_STATIC_INSTANCE

#define TS_STATIC_INSTANCE (   ObjectClass,
  ObjectArgs,
  StaticInstanceClass 
)

Local declaration of a static object regardless of modules initialization order.

The static initialization order nightmare

In C++, each module needs to be initialized. For each module, the compiler potentially generates an initialization routine. This initialization routine is responsible for constructing the static objects in the module. The initialization routines of all modules are executed before entering the main() function of the application.

A static object is defined inside one module. When the static object has an elementary type or a pointer type and is initialized by a static value (i.e. a compile-time, link-time or load-time expression), the initial value of the object is ready before the application starts, before the execution of all initialization routines of all modules. However, when the static object has a class type, its initialization is the invocation of a constructor. This constructor is invoked by the initialization routine of the module.

When a static object is used by the application, after entering the main() function of the application, it is guaranteed that all initialization routines have completed and, consequently, all static objects are fully constructed.

However, there are cases where a static object is used by the initialization routine of another module. This is typically the case when a static object references another static object in its constructor. Usually, this is not as obvious as it seems. If the constructor of a static object in module A invokes a static method from another module B, you do not know if this static method does or does not use a static object in the module B. In this case, for the application to work properly, it is necessary that the module B is initialized before the module A.

The problem is that there is no way in the C++ language to determine the execution order of the initialization routines of the various modules. The order is undefined and implementation dependent. In the previous example, it is possible that the initialization routine of the module B is executed before the initialization routine of the module A on a platform 1. On this platform, the application works correctly. But a platform 2 is also allowed to invoke the initialization routines in the reverse order. Consequently, on this platform, the application will most certainly crash.

To avoid this, you may simply ban all static objects in your coding rules. However, if you find this too restrictive, alternate solutions must be found.

The obvious solution is the singleton pattern (see the file tsSingletonManager.h). In this pattern, no object is constructed at initialization. A pointer is statically initialized to zero, which is guaranteed to work. The first time the object is requested, it is allocated, stored in this static pointer and reused every other time. However, for this to work in a multithreaded environment, the test-and-creation sequence must be protected by a mutex in the same module. And this mutex must be ... statically initialized !

So, there is still a requirement for a solution for the mutex, at least. And this solution is intrinsicly thread-unsafe. So, the solution must reduce the race condition risk to the minimum, typically by making the best effort to create the critical static objects as soon as possible, hopefully before the creation of the first thread.

This is the purpose of the macros TS_STATIC_INSTANCE_DECLARATION and TS_STATIC_INSTANCE_DEFINITION with the associated "shortcut" macro TS_STATIC_INSTANCE.

Using the TS_STATIC_INSTANCE macros

You need a static object. The type for this object is the parameter ObjectClass in the various TS_STATIC_INSTANCE macros.

The macros create an encapsulating local class for each static object. The name of this local class is the parameter StaticInstanceClass in the macros. This class has one public static method named Instance() which returns a reference to the static object. The static object is allocated during the initialization of the application, no later than during the initialization of the module where it is defined but possibly earlier if the static object is referenced before the initialization of the module where it is defined.

Thus, the initialization order problem is avoided. But, on the downside, the unique construction of the object is not thread-safe. Consequently, the object shall not be accessed by concurrent threads during the initialization phase of the application.

The initializers of the object are grouped into the parameter ObjectArgs of the macros. This is the argument list, including the pair of enclosing parentheses, of the constructor of the object.

The macro TS_STATIC_INSTANCE creates a static object inside the anonymous namespace of the module where it is expanded.

Example using the std::string type: Two static objects Foo1 and Foo2 are created. Remember that Foo1 and Foo2 are not the name of objects but the names of the encapsulating classes of the objects. The first static object is initialized by the default constructor and the parameter ObjectArgs of the macro is simply the empty argument list (). The second static object is built by the constructor std::string (size_t, char) and the parameter ObjectArgs is a list of two parameters (4, '=').

TS_STATIC_INSTANCE(std::string, (), Foo1)
TS_STATIC_INSTANCE(std::string, (4, '='), Foo2)
....
std::cout << "Foo1: " << Foo1::Instance() << std::endl;
std::cout << "Foo2: " << Foo2::Instance() << std::endl;
Parameters
ObjectClassThe name of the class of the static object. This must be an existing class.
ObjectArgsThe initializer list, enclosed within parentheses, of the constructor of the static instance. Use () if there is no initializer.
StaticInstanceClassThe name of the new class which encapsulates the static instance. This class is declared by the expansion of the macro.