fen
fen

Reputation: 10115

C++ static initialization order: adding into a map

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

But is this a problem in the following example?

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"?

Upvotes: 12

Views: 3935

Answers (2)

Wolf
Wolf

Reputation: 10238

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 void Register(string, func);

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

cpp file:

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

void Factory::Register(string name, func f)
{
    TheMap()[name] = f;
}

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.


Edit, 2022-09-01: I fixed issues in code (taken from the question). Now it would compile in the right context (not included here).

Upvotes: 9

Olaf Dietsche
Olaf Dietsche

Reputation: 74028

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.

Upvotes: 6

Related Questions