{"id":112709,"date":"2026-09-18T07:00:00","date_gmt":"2026-09-18T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112709"},"modified":"2026-09-21T07:22:19","modified_gmt":"2026-09-21T14:22:19","slug":"20260918-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260918-00\/?p=112709\/","title":{"rendered":"Comparing exception behavior of magic statics, <CODE>std::<WBR>call_<WBR>once<\/CODE>, and <CODE>std::<WBR>async<\/CODE>"},"content":{"rendered":"<p>We&#8217;ve been comparing <a title=\"Magic statics vs. std::call_once\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260916-00\/?p=112703\"> magic statics, <code>std::<wbr \/>call_<wbr \/>once<\/code><\/a>, and <a title=\"std::call_once vs. std::async\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260917-00\/?p=112706\"> <code>std::<wbr \/>async<\/code><\/a>, but one thing we haven&#8217;t considered is their behavior in the event of an exception.<\/p>\n<p>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.<\/p>\n<p>For <code>call_<wbr \/>once<\/code>, 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 <code>call_<wbr \/>once<\/code> with the same <code>once_<wbr \/>flag<\/code>, it will try to call it.<\/p>\n<p>But <code>std::<wbr \/>async<\/code> 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 <i>not<\/i> try to execute the invocable again.<\/p>\n<p>Let&#8217;s summarize this in a table.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>\u00a0<\/th>\n<th>Before<\/th>\n<th>After success<\/th>\n<th>After exception<\/th>\n<\/tr>\n<tr>\n<td>Magic static<\/td>\n<td>Uninitialized<\/td>\n<td>Initialized<\/td>\n<td>Uninitialized<\/td>\n<\/tr>\n<tr>\n<td><tt>std::<wbr \/>call_<wbr \/>once<\/tt><\/td>\n<td>Uninitialized<\/td>\n<td>Initialized<\/td>\n<td>Uninitialized<\/td>\n<\/tr>\n<tr>\n<td><tt>std::<wbr \/>async<\/tt><\/td>\n<td>Uninitialized<\/td>\n<td>Initialized<\/td>\n<td>Failed<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Or we can do it in a state diagram.<\/p>\n<table style=\"text-align: center;\" title=\"From a box labeled Uninitialized, there is an arrow labeled success that goes to Initialized. There is an arrow labeled async fail that goes to Failed. And an arrow labeled magic static fail and call_once fail that goes from Uninitialized back to itself.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>magic static fail<br \/>\n<tt>call_once<\/tt> fail<br \/>\n\u2b8f<\/td>\n<td valign=\"bottom\"><tt>async<\/tt> fail<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\">Uninitialized<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px currentcolor;\">Failed<\/td>\n<\/tr>\n<tr>\n<td>\u2193 success<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\">Initialized<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Going back to the choice between <code>std::<wbr \/>call_<wbr \/>once<\/code> and <code>std::<wbr \/>async<\/code>, 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 <code>std::<wbr \/>call_<wbr \/>once<\/code>. If you want to remember the failure and keep rethrowing it, then use <code>std::<wbr \/>async<\/code>.<\/p>\n<p>If you are indifferent, then I would suggest <code>std::<wbr \/>call_<wbr \/>once<\/code>, because it is much lighter weight.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Do you try again or give up?<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112709","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Do you try again or give up?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112709","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112709"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112709\/revisions"}],"predecessor-version":[{"id":112710,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112709\/revisions\/112710"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112709"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112709"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112709"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}