14

Once I was reading an awesome C++ FAQ (It is really good!!) and read the topic about how to prevent the static initialization order "fiasco". So the author advises to wrap the static variables into functions, thus to prevent the "fiasco" by maintaining the creation order of variables. But this seems to me a rude workaround. So my question is, is there any modern, more pattern oriented way to prevent this "fiasco" but to wrap the "static stuff" into functions???

learnvst
  • 15,455
  • 16
  • 74
  • 121
Eduard Rostomyan
  • 7,050
  • 2
  • 37
  • 76
  • 6
    The elegant way to prevent fiasco is to never use static objects that depend on anything. – eerorika Apr 23 '15 at 11:36
  • The FAQ advises about the *Construct on first use idiom*. A familar pattern to many C++ programmers. It's simple to implement and even simpler to use. I can't see what you mean by "modern, more pattern oriented way". – Not a real meerkat Apr 23 '15 at 11:42
  • 1
    Also see [AddressSanitizerInitializationOrderFiasco](https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco). – jww Jan 11 '16 at 10:31

4 Answers4

22

The modern, more pattern-oriented way is not to use globals in the first place.

There's no other way around it.

It wouldn't be much of a "fiasco", otherwise!

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Unfortunately, this is not realistic. It does not help thousands of packages in a typical repo or GNU's FTP site. – jww Jan 11 '16 at 10:32
  • @jww: The only reasonable alternative is to do what the OP themselves suggest in the question. – Lightness Races in Orbit Jan 11 '16 at 11:01
  • 1
    That's a false choice. It moves the problem around so the crash is experienced in the destructor; and not a constructor. I've experienced the crash in the dtor first hand when an object disappears too soon. – jww Jan 11 '16 at 13:09
  • 3
    @jww Code with that problem has serious inter-dependency problems and should be refactored. – Lightness Races in Orbit Jan 11 '16 at 13:32
6

So my question is, is there any modern, more pattern oriented way to prevent this "fiasco" but to wrap the "static stuff" into functions???

In most cases, you can declare your "global" data in the main function, and use dependency injection to pass it around, where needed. In other words, do not have static state at all.

In practice, you can have situations where static data is needed. If there are no dependencies to other statics, make the static data const/constexpr.

// smart pointer that implements the "Foo" release policy
class FooPointer
{
    static const FooPointer NullFoo; // does not depend on other static values
    /* ... */
};

In case the static variables do depend on each other, just wrap them in static functions:

// smart pointer that implements the "Foo" release policy
class FooPointer
{
    static const FooPointer& NullFoo(); // depends on other static values
    /* ... */
};

To summarize:

Most (90%? 99%?) static/global/shared data should be dependency-injected into where it is used, and not created as static at all.

In the rare cases when statics are required for a reason or another and they do not depend on other statics, declare static variables.

In the very rare cases when statics need to be static and they depend on each other, wap them in static methods.

As a rule of thumb, if you have a lot of the second and third cases, you are not doing enough of the first.

utnapistim
  • 26,809
  • 3
  • 46
  • 82
2

The more usual way of addressing the problem is to avoid statics whenever possible - and even more so between objects that rely on construction order.

Then construct objects in the required order. For example, if we have two objects x and y, and construction of y will fail if x has not been constructed, then construct x first and supply it to the constructor (or another member) of y)

 SomeObject x;
 SomeOtherObject y(x);

or

 SomeObject *x = new SomeObject;
 SomeOtherObject y = new SomeObject(*x);   

(both of the above assume the constructor of y requires a reference).

If you need to share x and y between functions, simply pass them to functions as arguments.

If you must use statics (i.e. you don't want the typing of passing arguments everywhere) make the statics to be pointers, and initialise them once (for example, in main()).

//  all source files can use x and y via these declarations  (e.g. via a header file)

extern SomeObject *x;
extern SomeOtherObject *y;

//  definition in one source file only

SomeObject *x;
SomeOtherObject *y;

int main()
{
     x = new SomeObject;
     y = new SomeOtherObject(*x);

       // call other functions that use x and y.

     delete y;
     delete x;
}

But, really, it is best to avoid using statics if at all possible.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • +1 This is a better approach than the construct on first use idiom IMHO. A possible improvement when scaling up to larger projects is to use initialisation and accessor functions (to hide the pointer and assert that initialisation was done) and grouping initialisations by library. However, the basic idea is the same: take control of when the initialisation and clean up is done and don't leave it to random chance. – markh44 Apr 28 '20 at 14:38
1

Not an exact answer, but useful trick i am quoting from cppstories which uses constinit feature from C++20

constinit forces constant initialization of static or thread-local variables. It can help to limit static order initialization fiasco by using precompiled values and well-defined order rather than dynamic initialization and linking order…

#include <array>

// init at compile time
constexpr int compute(int v) { return v*v*v; }
constinit int global = compute(10);

// won't work:
// constinit int another = global;

int main() {
    // but allow to change later...
    global = 100;

    // global is not constant!
    // std::array<int, global> arr;
}
Bharat
  • 386
  • 5
  • 20