C++ static initialization order
Asked Answered
U

6

82

When I use static variables in C++, I often end up wanting to initialize one variable passing another to its constructor. In other words, I want to create static instances that depend on each other.

Within a single .cpp or .h file this is not a problem: the instances will be created in the order they are declared. However, when you want to initialize a static instance with an instance in another compilation unit, the order seems impossible to specify. The result is that, depending on the weather, it can happen that the instance that depends on another is constructed, and only afterwards the other instance is constructed. The result is that the first instance is initialized incorrectly.

Does anyone know how to ensure that static objects are created in the correct order? I have searched a long time for a solution, trying all of them (including the Schwarz Counter solution), but I begin to doubt there is one that really works.

One possibility is the trick with the static function member:

Type& globalObject()
{
    static Type theOneAndOnlyInstance;
    return theOneAndOnlyInstance;
}

Indeed, this does work. Regrettably, you have to write globalObject().MemberFunction(), instead of globalObject.MemberFunction(), resulting in somewhat confusing and inelegant client code.

Update: Thank you for your reactions. Regrettably, it indeed seems like I have answered my own question. I guess I'll have to learn to live with it...

Unbeatable answered 17/6, 2009 at 8:0 Comment(1)
the instances will be created in the order they are definedDemott
U
77

You have answered your own question. Static initialization order is undefined, and the most elegant way around it (while still doing static initialization i.e. not refactoring it away completely) is to wrap the initialization in a function.

Read the C++ FAQ items starting from https://isocpp.org/wiki/faq/ctors#static-init-order

Ursa answered 17/6, 2009 at 8:9 Comment(8)
@CharlesSalvia Bringing you back 2.5 years in time :), Is it really a problem that this is not thread-safe? We're talking about static initialisation, so, this is all happening before main(). Should we really be concerned about thread safety issues before main()?Helvetic
not before main enter, but when the method is first called. see blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspxErasure
@enobayram: Bringing you back 2.1 years in time :), Yes, it's a problem. The whole point of moving the initialisation into a function is that the initialisation now occurs not before main, but when the function is first invoked, which may be at any point during the program's execution... and in any thread.Sensorimotor
@Lightness, Jose, Charles: I really think your comments should be incorporated in the accepted answer. With multithreading being commonplace today this can cause nasty surprises and isn't mentioned in any of the top search results for the static initialization fiasco. i.e. parashift doesn't mention it at all.Disobedience
Thread-safety isn't a problem in C++11. See #8102625Flyblow
@laalto, reading the C++ FAQ puts us back in the "have you read the man pages" days.Chemaram
Though it's possible to make those wrapper functions call first time before main if we want it -- in a constructor of a static classUnstained
"The most elegant way around it (..) is to wrap the initialization in a function." then you have fallen into static deinitialization order disaster if your object has not been constructed at the moment of destructing the calling objectBushwhacker
C
7

Maybe you should reconsider whether you need so many global static variables. While they can sometimes be useful, often it's much simpler to refactor them to a smaller local scope, especially if you find that some static variables depend on others.

But you're right, there's no way to ensure a particular order of initialization, and so if your heart is set on it, keeping the initialization in a function, like you mentioned, is probably the simplest way.

Clo answered 17/6, 2009 at 8:12 Comment(2)
You are right, it is unwise to use too much global static variables, but for some cases it avoids having to pass the same object around excessively. Think of the logger object, the container for persistent variables, the collection of all IPC connections, etc...Unbeatable
"Avoids passing an object excessively" is just a way of saying, "the dependencies between the components of my program are so extensive that tracking them is excessive work. So it's better to stop tracking them". Except that usually it isn't better -- if the dependencies cannot be simplified then that's when it's most useful to be able to track them down by following where the object is passed.Philistine
E
7

Most compilers (linkers) actually do support a (non-portable) way of specifying the order. For example, with visual studio you can use the init_seg pragma to arrange the initialization into several different groups. AFAIK there is no way to guarantee order WITHIN each group. Since this is non-portable you may want to consider if you can fix your design to not require it, but the option is out there.

Eloyelreath answered 26/6, 2009 at 14:42 Comment(0)
H
5

Indeed, this does work. Regrettably, you have to write globalObject().MemberFunction(), instead of globalObject.MemberFunction(), resulting in somewhat confusing and inelegant client code.

But the most important thing is that it works, and that it is failure proof, ie. it is not easy to bypass the correct usage.

Program correctness should be your first priority. Also, IMHO, the () above is purely stylistic - ie. completely unimportant.

Depending on your platform, be careful of too much dynamic initialization. There is a relatively small amount of clean up that can take place for dynamic initializers (see here). You can solve this problem using a global object container that contains members different global objects. You therefore have:

Globals & getGlobals ()
{
  static Globals cache;
  return cache;
}

There is only one call to ~Globals() in order to clean up for all global objects in your program. In order to access a global you still have something like:

getGlobals().configuration.memberFunction ();

If you really wanted you could wrap this in a macro to save a tiny bit of typing using a macro:

#define GLOBAL(X) getGlobals().#X
GLOBAL(object).memberFunction ();

Although, this is just syntactic sugar on your initial solution.

Hercegovina answered 17/6, 2009 at 10:12 Comment(0)
A
3

dispite the age of this thread, I would like to propose the solution I've found. As many have pointed out before of me, C++ doesn't provide any mechanism for static initialization ordering. What I propose is to encapsule each static member inside a static method of the class that in turn initialize the member and provide an access in an object-oriented fashion. Let me give you an example, supposing we want to define the class named "Math" which, among the other members, contains "PI":

class Math {
public:
   static const float Pi() {
       static const float s_PI = 3.14f;
       return s_PI;
   }
}

s_PI will be initialized the first time Pi() method is invoked (in GCC). Be aware: the local objects with static storage have an implementation dependent lifecyle, for further detail check 6.7.4 in 2.

Static keyword, C++ Standard

Acinaciform answered 12/8, 2013 at 10:29 Comment(2)
How is this different fro, OP's approach, except that you made the function a member of a class?Jehu
also not an illuminating example, since an object like that can and should be constexprHoskins
C
1

Wrapping the static in a method will fix the order problem, but it isn't thread safe as others have pointed out but you can do this to also make it thread if that is a concern.

// File scope static pointer is thread safe and is initialized first.
static Type * theOneAndOnlyInstance = 0;

Type& globalObject()
{
    if(theOneAndOnlyInstance == 0)
    {
         // Put mutex lock here for thread safety
         theOneAndOnlyInstance = new Type();
    }

    return *theOneAndOnlyInstance;
}
Canica answered 29/1, 2014 at 16:45 Comment(2)
The top answer suggests exactly this, and it was answered nearly 5 years ago. Perhaps we should move this into the top answer as an example.Melodimelodia
This is not a problem in C++11. See #8102625Flyblow

© 2022 - 2024 — McMap. All rights reserved.