Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ static initialization order: adding into a map

Tags:

c++

We cannot determine the order of the initialization of static objects.

But is this a problem in the following example?

  • one static variable is a map (or other container)
  • from other static variable we populate that map

the code:

class Factory
{
public:
    static bool Register(name, func);

private:
    static map<string, func> s_map;
};

// in cpp file
map<string, func> Factory::s_map;

bool Factory::Register(name, func)
{
    s_map[name] = func;
}

and in another cpp file

static bool registered = Factory::Register("myType", MyTypeCreate);

When I register more types I don't depend on the order in the container. But what about the first addition to the container? Can I be sure it's initialized "enough" to take the first element?

Or it's another problem of "static initialization order fiasco"?

like image 271
fen Avatar asked Feb 02 '18 08:02

fen


2 Answers

Your scenario is not guaranteed to work as expected. The success depends on the link order.

One approach to be sure is to access the map through a (static) function that creates the object as a static variable like this:

h file:

class Factory
{
public:
    static bool Register(name, func);

private:
    static map<string, func>& TheMap();
};

cpp file:

map<string, func>& Factory::TheMap()
{
    static map<string, func> g_;
    return g_;
}

bool Factory::Register(name, func)
{
    TheMap()[name] = func;
}

The downside of this is that the order of destruction of static variables is hard to control by you as the developer. In the case of the map this is no problem. But if static variables reference each other, the "static linking fiasco" gets even worse: In my experience it's much harder to prevent/debug a crash when a program ends compared to when it starts.

like image 155
Wolf Avatar answered Oct 13 '22 00:10

Wolf


Being lazy, here's a copy from http://en.cppreference.com/:

Non-local variables

All non-local variables with static storage duration are initialized as part of program startup, before the execution of the main function begins (unless deferred, see below).

...

Dynamic initialization

After all static initialization is completed, dynamic initialization of non-local variables occurs in the following situations:

...

Deferred dynamic initialization

It is implementation-defined whether dynamic initialization happens-before the first statement of the main function (for statics) or the initial function of the thread (for thread-locals), or deferred to happen after.

If the initialization of a non-inline variable is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized.

The important part is odr-use:

ODR-use

Informally, an object is odr-used if its value is read (unless it is a compile time constant) or written, its address is taken, or a reference is bound to it;

Since the s_map is populated through Factory::Register, I don't see a problem here.


If the map implementation is very trivial, it may even be initialized as part of the static initialization/at compile time.

But even if the initialization is deferred, it will be initialized before the use in Factory::Register, as long as both are in the same translation unit.

However, if the map is defined in one translation unit, and Factory::Register is defined in another, anything can happen.

like image 44
Olaf Dietsche Avatar answered Oct 12 '22 23:10

Olaf Dietsche