6 comments

  • halayli49 minutes ago
    The performance observation is real but the two approaches are not equivalent, and the article doesn&#x27;t mention what you&#x27;re actually trading away, which is the part that matters.<p>The C++11 threadsafety guarantee on static initialization is explicitly scoped to block local statics. That&#x27;s not an implementation detail, that&#x27;s the guarantee.<p>The __cxa_guard_acquire&#x2F;release machinery in the assembly is the standard fulfilling that contract. Move to a private static data member and you&#x27;re outside that guarantee entirely. You&#x27;ve quietly handed that responsibility back to yourself.<p>Then there&#x27;s the static initialization order fiasco, which is the whole reason the meyers singleton with a local static became canonical. Block local static initializes on first use, lazily, deterministically, thread safely. A static data member initializes at startup in an order that is undefined across translation units. If anything touches Instance() during its own static initialization from a different TU, you&#x27;re in UB territory. The article doesn&#x27;t mention this.<p>Real world singleton designs also need: deferred&#x2F;configuration-driven initialization, optional instantiation, state recycling, controlled teardown. A block local static keeps those doors open. A static data member initializes unconditionally at startup, you&#x27;ve lost lazy-init, you&#x27;ve lost the option to not initialize it, and configuration based instantiation becomes awkward by design.<p>Honestly, if you&#x27;re bottlenecking on singleton access, that&#x27;s design smell worth addressing, not the guard variable.
    • alex_dev4223 minutes ago
      Excellent points about the initialization order fiasco. I&#x27;ve been bitten by this in embedded systems where startup timing is critical.<p>One thing I&#x27;d add: the guard overhead can actually matter in high-frequency scenarios. I once profiled a logging singleton that was called millions of times per second in a real-time system - the atomic check was showing up as ~3% CPU. But your point stands: if you&#x27;re hitting that bottleneck, you probably want to reconsider the architecture entirely.<p>The lazy initialization guarantee is usually worth more than the performance gain, especially since most singletons aren&#x27;t accessed in tight loops. The static member approach feels like premature optimization unless you&#x27;ve actually measured the guard overhead in your specific use case.
    • csegaults26 minutes ago
      Err how does the static approach suffer from thread safety issues when the initialization happens before main even runs?<p>I might be responding to a llm so...
      • halayli8 minutes ago
        a real human. threads can exist before main() starts. for example, you can include another tu which happens to launch a thread and call instance(). Singletons used to be a headache before C++11 and it was common(maybe still is) to see macros in projects that expand to a singleton class definition to avoid common pitfalls.
      • platinumrad22 minutes ago
        It&#x27;s a bit contrived, but a global with a nontrivial constructor can spawn a thread that uses another global, and without synchronization the thread can see an uninitialized or partially initialized value.
  • m-schuetz1 hour ago
    I liked using singletons back in the day, but now I simply make a struct with static members which serves the same purpose with less verbose code. Initialization order doesn&#x27;t matter if you add one explicit (and also static) init function, or a lazy initialization check.
  • platinumrad52 minutes ago
    I haven&#x27;t written C++ in a long time, but isn&#x27;t the issue here that the initialization order of globals in different translation units is unspecified? Lazy initialization avoids that problem at very modest cost.
  • silverstream2 hours ago
    Honestly the guard overhead is a non-issue in practice — it&#x27;s one atomic check after first init. The real problem with the static data member approach is initialization order across translation units. If singleton A touches singleton B during startup you get fun segfaults that only show up in release builds with a different link order.<p>I ended up using std::call_once for those cases. More boilerplate but at least you&#x27;re not debugging init order at 2am.
    • csegaults51 minutes ago
      Came here to say the same thing. Static is OK as long as the object has no dependencies but as soon as it does you&#x27;re asking for trouble. Second the call_once approach. Another approach is an explicit initialization order system that ensures dependencies are set up in the right order, but that&#x27;s more complex and only works for binaries you control.
  • swaminarayan1 hour ago
    Nice breakdown. I’m curious how often the guard check for a function-local static actually shows up in real profiles. In most codebases Instance() isn’t called in tight loops, so the safety of lazy initialization might matter more than a few extra instructions. Has anyone run into this being a real bottleneck in practice?
  • signa111 hour ago
    i am not sure why this entire article is warranted :o) just use `std::call_once` and you are all set.