Introduction
If exactly one instance of some class is needed by your program, a singleton is often used as a seemingly reasonable soluti...
For further actions, you may consider blocking this person and/or reporting abuse
Hello, thank you for providing such a detailed introduction to Singleton. I went through your code and did some experiments. However, I suspect that the generalized nifty counter does not work (or maybe I missed something). The problem is here.
Notice that we are initializing variable
stream_init
using external referencestream
in the header file (which is included by every .cpp files using stream, including Stream.cpp). Because we initializestream
inStream.cpp
. It's possible thatstream
has not been initialized (i.e., bind to the buffer) at the time we are using it to constructstream_init
, since the order of construction of static objects are not well defined across translation unit. In such case,stream
will refer to nullptr and the program will crash with segfault during placement new.While the code works fine out of the box, to confirm my suspicion that it works due to luck, I added an output statement to the
ref()
method insingleton_buf
while still maintaining itsconstexpr
status, and the program crashed with segfault as expected.It also failed if I don't modify the function body, but remove the
constexpr
qualification onref()
. So maybe the original code happened to work due to some compiler optimization (that I am not sure whether it's backed by the C++ standard).Note that I am using C++17 (macOS, clang 15.0, CLion default configuration), so I replaced concept
<Singleton T>
with<typename T, typename = std::enable_if_t<std::is_base_of_v<SingletonCounter, T>
. But this shouldn't affect the test.Yes, I know that the order of construction of static objects is undefined across TUs. Perhaps this was another way the original code was also wrong.
I've made 3 changes:
std::byte
tochar
(necessary for #2).Changed the constructor to:
(necessary for #3).
Added
constinit
:See if that helps. Unfortunately, there's no way to make
ref()
beconstexpr
sincereinterpret_cast
s aren't allowed inconstexpr
functions.The modification I proposed that breaks the code is actually my fault, print things in constexpr is not allowed by the standard, and my compiler is lenient enough to allow it. But stricter compiler would have throw compiler errors. I am not sure why standard does not allow
reinterpret_cast
in the constexpr function (perhaps due to potential UBs).I did a bit more research and experimentation. The original code does work because
reinterpret_cast<Stream&>(stream_buf)
is actually a compile time value. There is no problem for compiler to compute the address of that static buffer and assign this value to thestream
. This is vaguely mentioned in the standard as "constant initialization" instead of "zero initialization". You can clearly see from the compiler explorer that the value is set to the address vs zero. godbolt.org/z/PdThb59z5What worries me is that gcc and clang disagree on whether to use "constant initialization" vs "zero initialization" in many cases (from the above compiler explorer link). This means such behavior is more or less implementation defined and possible not portable. But both of them agree on using "constant initialization" for the case in the original code. MSVC is a bit weird and I did not test it.
For some reason, I never got a notification of your reply. I just stumbled across it today. Anyway....
I'd need to see the relevant section of the standard to know whether it's implementation-defined or not. If not, then either gcc or clang is wrong and I'd hope that they'd eventually fix the bug. I'd guess that it's not implementation-defined, i.e., is explicitly specified by the standard since it seems like too-important a detail.
Hello, Thank you for your time to explain how to implement the singleton «à la» cout. very instructive, and could not agree more with you: singletons should be use sparingly!
Now I have a problem with MSVC 2022, I cannot use
as the stream reference is not initialized and singleton_init receive a nullptr.
using std::byte, and removing const does the trick
as in:
So I'm not sure that even if GCC and Clang do the compile initialization, maybe the standard does not mandate it ?
I'm compiling without optimization, with optimization there is no problems!
Are you saying my original code compiles and works with optimization?
without optimisation, the init is called before the reference memory has been bound to the reference, so it crashes.
With optimization, MSVC does init the reference to the proper block of memory at compile time, so the init receive a proper pointer!
Also, your reinterpret_cast is not good as it cast away constness of the byte array.
Why in the first place do you declare the byte array const ? maybe I'm missing something
Good tutorial. Now never ever ever ever ever use it.
You did read the “Specific Cases for Singletons” section, right?
Yes. That is a good section. It also largely neuters much of the reason people want to use singletons. I've generally leaned the direction of "we just don't need singletons anymore", and it hasn't hurt me. Sure, I'll consume a singleton if I have to. I can't control what other people do, but I'm not going to add to the singleton dumpster fire.
I really like the template
but i just wonder do we need to handle the copy constructor and assignment operator ?
Actually, they should be handled for all the classes the same way: forbid them. Fixed. Thanks.
Hi, thank you for this really interesting article, I'm developing in C++17 and unfortunately I have to work with a singleton and unfortunately I can't use concepts. I'm completely inexperienced with the semantics of concepts, I can be called a youngling :p, is there a way to define the Singleton template in a similar way to what you defined using concepts, but in a C++17 compliant way?
thank you Master
You just use
typename
instead. Concepts only add additional constraints to types (which is a good thing), but plaintypename
will still work.