Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 Singleton. Static variable is thread safe? Why?

Tags:

c++11

I just read, that this construct:

const bg::AppSettings& bg::AppSettings::GetInstance()
{
    static AppSettings instance;
    return instance;
}

is a thread safe and working way to create a singleton?! Am I correct, that the static AppSettings variable will be the same, every time I call this method?! I get a little bit confused about the scoping on this one ...

My normal approach was to use a unique_ptr as a static member of my class ... but this seems to work...can someone explain to me, what's going on here?!

And btw.: does the const make sense here?!

like image 894
DoubleVoid Avatar asked Dec 24 '15 20:12

DoubleVoid


2 Answers

In C++11 (and forward), the construction of the function local static AppSettings is guaranteed to be thread-safe. Note: Visual Studio did not implement this aspect of C++11 until VS-2015.

The compiler will lay down a hidden flag along side of AppSettings that indicates whether it is:

  • Not constructed.
  • Being constructed.
  • Is constructed.

The first thread through will find the flag set to "not constructed" and attempt to construct the object. Upon successful construction the flag will be set to "is constructed". If another thread comes along and finds the flag set to "being constructed", it will wait until the flag is set to "is constructed".

If the construction fails with an exception, the flag will be set to "not constructed", and construction will be retried on the next pass through (either on the same thread or a different thread).

The object instance will remain constructed for the remainder of your program, until main() returns, at which time instance will be destructed.

Every time any thread of execution passes through AppSettings::GetInstance(), it will reference the exact same object.

In C++98/03, the construction was not guaranteed to be thread safe.

If the constructor of AppSettings recursively enters AppSettings::GetInstance(), the behavior is undefined.

If the compiler can see how to construct instance "at compile time", it is allowed to.

If AppSettings has a constexpr constructor (the one used to construct instance), and the instance is qualified with constexpr, the compiler is required to construct instance at compile time. If instance is constructed at compile time, the "not-constructed/constructed" flag will be optimized away.

like image 75
Howard Hinnant Avatar answered Oct 12 '22 13:10

Howard Hinnant


The behavior of your code is similar to this:

namespace {
    std::atomic_flag initialized = ATOMIC_FLAG_INIT;
    std::experimental::optional<bg::AppSettings> optional_instance;
}

const bg::AppSettings& bg::AppSettings::GetInstance()
{
    if (!initialized.test_and_set()) {
        optional_instance.emplace();
    }
    return *optional_instance;
}

By having a thread-safe flag that lives for the entire duration of the program, the compiler can check this flag each time the function is called and only initialize your variable once. A real implementation can use other mechanisms to get this same effect though.

like image 26
Vaughn Cato Avatar answered Oct 12 '22 14:10

Vaughn Cato