September 18th, 2026
like1 reaction

Comparing exception behavior of magic statics, std::call_once, and std::async

We’ve been comparing magic statics, std::call_once, and std::async, 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_once, 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_once with the same once_flag, it will try to call it.

But std::async 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::call_once Uninitialized Initialized Uninitialized
std::async 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::call_once and std::async, 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::call_once. If you want to remember the failure and keep rethrowing it, then use std::async.

If you are indifferent, then I would suggest std::call_once, because it is much lighter weight.

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

3 comments

Sort by :
  • Yexuan Xiao 13 hours ago

    If it is only for caching a variable, and the variable is trivial, using std::atomic has the best performance.

    • Raymond ChenMicrosoft employee Author

      Can you elaborate? How can you tell whether a std::atomic has been initialized?

      • Yexuan Xiao

        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.