Code Yarns โ€๐Ÿ‘จโ€๐Ÿ’ป
Tech Blog โ– Personal Blog

C++: Static Initialization Order

๐Ÿ“… 2010-May-04 โฌฉ โœ๏ธ Ashwin Nanjappa โฌฉ ๐Ÿ“š Archive

If you are using a static or global object from one translation unit in another translation unit, you are in trouble.

For example:

// Point2D.h
class Point2D
{
private:
    int x_, y_;
public:
    Point2D(int x, int y) : x_(x), y_(y) {}
    static const Point2D INVALID;
};
// Point2D.cpp
#include "Point2D.h"
const Point2D::INVALID(-1, -1); // Initialize static object
// Main.h
#include "Foobar.h"
#include "Point2D.h"
class Main
{
public:
    static Foobar foobar;
}
// Main.cpp
#include "Main.h"

// Point2D::INVALID may not even be initialized to (-1, -1) when foobar is constructed!
Foobar Main::foobar(Point2D::INVALID);

In the above example, you are bitten by the static initialization order problem in C++. The problem is there is no initialization order for static globals, the order is undefined in C++.

Point2D and Main classes are defined in different source files, or in C/C++ parlance, called different translation units. So, the Main::foobar object could be created even before the Point2D::INVALID object is created.

A popular solution for such cross-translation unit initializations seems to be the use of a static method that returns a reference to a function-local static object. That is, we are essentially creating a singleton which creates the static object lazily: the first time the singleton function is called. This technique is called lazy initialization and the static object is sometimes called magic static. The function-local static object is guaranteed to be created only once, the first time that function is called.

Applying this method to our problem, we change Point2D::INVALID into a function:

// Point2D.h
class Point2D
{
private:
    int x_, y_;
public:
    Point2D(int x, int y) : x_(x), y_(y) {}
    static Point2D& INVALID()
    {
        static Point2D* pt = new Point2D(-1, -1);
        return *pt;
    }
};

We will need to replace all usages of Point2D::INVALID with Point2D::INVALID() after this change.

However, there is still a problem: what is two threads call and enter the singleton function at the same time? Can we make this thread-safe without resorting to locking or mutual exclusion?

Thankfully, if you are using a C++11 or later compiler you do not need to do anything. This is because the C++11 standard guarantees that if multiple threads enter the singleton function for the first time, the static object is thread-safe and initialized only once.

Here is what the C++11 standard says:

ยง6.7 If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

Beware that not all compilers that say that they support C++11 might support this thread safety. For example, compilers earlier than Visual C++ 2015 do not support thread safety for magic static, though they might be C++11.

References:

For more on this, check out Item 4: Make sure the objects are initialized before they are used from the book Effective C++ (3rd Edition) by Scott Meyers. He uses a function-local static object, instead of a function-local static pointer. Using a pointer seems to be a better strategy as explained in the answer to this question at C++ FAQ Lite.