undermind
undermind

Reputation: 1841

Thread-safe initialization of static variables using local static std::once_flag and local static pointer

I'm using Visual Studio 2013, which doesn't have "magic statics" feature implemented yet, so local static variables initialization isn't yet thread-safe. So, instead of

Foo& GetInstance()
{
    static Foo foo;
    return foo;
}

I do something like this:

std::unique_ptr<Foo> gp_foo;
std::once_flag g_flag;

Foo& GetInstance()
{
    std::call_once(g_flag, [](){ gp_foo = std::make_unique<Foo>(); });    
    return *gp_foo;
}

But I don't like the idea of having gp_foo and g_flag global variables (first, problem with the order of initialization of static variables in different translation units; second, I would like to initialize variables only when we need them, i.e. after first call to GetInstance()), so I implemented the following:

Foo& GetInstance()
{
    // I replaced a smart pointer 
    // with a raw one just to be more "safe"
    // not sure such replacing is really needed
    static Foo *p_foo = nullptr;

    static std::once_flag flag;
    std::call_once(flag, [](){ p_foo = new Foo; });    
    return *p_foo;
}

And it seems to work (at least it passes the tests), but I'm not sure it's thread-safe, because here we have the same potential problem with the initialization of static local variables p_foo and flag in multiple threads. Initialization of raw pointer with nullptr and initialization of std::once_flag seems more innocent than calling Foo's constructor, but I would like to know whether it is really safe.

So, are there any problems with the last code snippet?

Upvotes: 2

Views: 1525

Answers (3)

LWimsey
LWimsey

Reputation: 6647

Your last code snippet is fine from a thread-safe initialization point of view.

However, it is not clear how are you going to use the Foo object in threads calling GetInstance. Since you are returning a reference to a non-const object, I suppose threads may modify the Foo object. Keep in mind that you need additional synchronization for this (eg. a mutex)

If the Foo object is fully initialized by its constructor and threads calling GetInstance will only read from the object, there is no problem but I would suggest to return const Foo &

Upvotes: 0

Richard Hodges
Richard Hodges

Reputation: 69882

By far the most stable approach to singleton object initialisation is the schwartz_counter. It's how std::cin, cout etc are implemented and how they always work, regardless of initialisation order of global objects.

It works in all versions of c++.

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter

Upvotes: 1

Griffin
Griffin

Reputation: 766

If Foo& GetInstance() is only part of the same compilation unit then the initialization order is defined hence it is thread safe.

However, if above is not the case and multiple compilation units are referencing that then the initialization order would depend on the order of the calls to Foo& GetInstance() and if multiple threads are involved then the order is undefined hence not thread safe.

Worth checking:

Upvotes: 0

Related Questions