8

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?!

DoubleVoid
  • 777
  • 1
  • 16
  • 46
  • 1
    Related: http://stackoverflow.com/questions/1962880/is-c-static-member-variable-initialization-thread-safe – clcto Dec 24 '15 at 20:11
  • 1
    Yes. It is thread safe. Also, note that instance will be initialised in the first time GetInstance() is called, not on the program startup. It might be an undesired behaviour in some cases. – André Puel Dec 24 '15 at 20:13
  • So if I am correct: "instance" gets created when the function is first called and does not need any mutex. If so, then this is way too simple x_X EDIT: Ah you were faster André :) I guess for a simple AppSettings class that reads the settings from a file, it does not matter ... I hope ... – DoubleVoid Dec 24 '15 at 20:14
  • Because the standard guarantees it. Is that enough reason for you? – Isaac Dec 24 '15 at 22:10
  • 1
    @Isaac, not really, as Howard Hinnat states: Visual Studio did not implement this aspect of C++11 until VS-2015. – DoubleVoid Dec 25 '15 at 01:28

2 Answers2

12

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    But what if one thread sees "not constructed" and then a task switch occurs before it sets the flag to "is constructing"? Another thread could see "not constructed" and start constructing, then if a task switch back to the first thread happens before construction is finished, won't it overwrite the object? What is protecting this from happening? – Remy Lebeau Dec 24 '15 at 20:58
  • 1
    @RemyLebeau: The setting/reading of the flag itself is thread safe. This can be achieved in a variety of implementation-specific means such as atomics, double-checked-locking, etc. The standard requires that it work, but does not specify how. It is typically implemented slightly different for each hardware platform. Here is a popular ABI interface for the implementation: http://mentorembedded.github.io/cxx-abi/abi.html#once-ctor – Howard Hinnant Dec 24 '15 at 21:07
  • 1
    Thank you very much. This explains the behavior very well! :) Maybe it's worth checking out the generated assembler code. – DoubleVoid Dec 25 '15 at 01:30
  • 1
    @DoubleVoid: Oh absolutely! Never hesitate to checkout the generated assembler! That *is* the **ultimate** answer. And not enough programmers bother. Kudos for moving in that direction! :-) – Howard Hinnant Dec 25 '15 at 01:43
3

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.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132