We’ve been comparing magic statics, std::, and std::, but one thing we haven’t considered is their behavior in the event of an exception.
For magic statics, if an exception occurs during initialization of the static, then the static is considered not to have been initialized. The exception propagates, and the next time the function is called, the language will try to initialize the static again.
For call_, if an exception occurs during execution of the lambda, then the call is considered not to have occurred. The exception propagates, so the next time you call call_ with the same once_, it will try to call it.
But std:: is different. If an exception occurs during execution of the invocable, then the exception is saved, and when you ask the future or shared future for the result, the exception is rethrown. It does not try to execute the invocable again.
Let’s summarize this in a table.
| Before | After success | After exception | |
|---|---|---|---|
| Magic static | Uninitialized | Initialized | Uninitialized |
| std:: |
Uninitialized | Initialized | Uninitialized |
| std:: |
Uninitialized | Initialized | Failed |
Or we can do it in a state diagram.
| magic static fail call_once fail ⮏ |
async fail | |
| Uninitialized | → | Failed |
| ↓ success | ||
| Initialized |
Going back to the choice between std:: and std::, you have to think about what you want to happen if an exception occurs while trying to initialize the variable. If you want to try again, then use std::. If you want to remember the failure and keep rethrowing it, then use std::.
If you are indifferent, then I would suggest std::, because it is much lighter weight.
With all due respect sir — what does std::async have to do with std::call_once or magic local statics?
You would almost never interchangeably swap std::async with the other two.
This article is very confusing as a result of that fact…
Oh never mind. I read your other article behind the std::async hyperlink. Yeah.. I suppose you can do that. But that’s pretty nasty stuff and kind of at the margins for what std::async was even designed for.
If it is only for caching a variable, and the variable is trivial, using std::atomic has the best performance.
Well yeah, if you can reserve a sentinel value, then that makes it easier. And the simple version you used requires that the calculation be safe to repeat, so you don’t want to use it for something like a registration token, since it would mean that if two threads both try to initialize it, you register twice but save only one of the registration tokens (so you can unregister at most one of them).
Can you elaborate? How can you tell whether a std::atomic has been initialized?
Use a sentinel, such as 0 or the maximum value. STL uses a static std::atomic to cache the result of QueryPerformanceFrequency https :// github.com/microsoft/ STL/blob/85f13f3563be49535320a0d31c095405185ee08c/stl/src/xtime.cpp#L97-L107 . The value must be initialized to the sentinel, and then checked; if it is the sentinel, then set it. https :// godbolt.org/z/r4zr9oE7s show the difference in the generated code between atomic and static magic.
this comment has been deleted.