{"id":112266,"date":"2026-04-24T07:00:00","date_gmt":"2026-04-24T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112266"},"modified":"2026-04-24T08:05:11","modified_gmt":"2026-04-24T15:05:11","slug":"20260424-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260424-00\/?p=112266","title":{"rendered":"Defending against exceptions in a <CODE>scope_exit<\/CODE> RAII type"},"content":{"rendered":"<p>One of the handy helpers in the <a href=\"https:\/\/github.com\/microsoft\/wil\/\"> Windows Implementation Library<\/a> (WIL) is <code>wil::<wbr \/>scope_<wbr \/>exit<\/code>. We&#8217;ve used it to <a title=\"All the other cool languages have try.finally. C++ says &quot;We have try.finally at home.&quot;\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251222-00\/?p=111890\"> simulate the <code>finally<\/code> keyword in other languages<\/a> by arranging for code to run when control leaves a scope.<\/p>\n<p>I&#8217;ve identified three places where exceptions can occur when using <code>scope_<wbr \/>exit<\/code>.<\/p>\n<pre>auto cleanup = wil::scope_exit([captures] { action; });\r\n<\/pre>\n<p>One is at the construction of the lambda. What happens if an exception occurs during the initialization of the captures?<\/p>\n<p>This exception occurs even before <code>scope_<wbr \/>exit<\/code> is called, so there&#8217;s nothing that <code>scope_<wbr \/>exit<\/code> can do. The exception propagates outward, and the action is never performed.<\/p>\n<p>Another is at the point the <code>scope_<wbr \/>exit<\/code> tries to move the lambda into <code>cleanup<\/code>. In a na\u00efve implementation of <code>scope_<wbr \/>exit<\/code>, the exception would propagate outward without the action ever being performed.<\/p>\n<p>The third point is when the <code>scope_<wbr \/>exit<\/code> is destructed. In that case, it&#8217;s an exception thrown from a destructor. Since destructors default to <code>noexcept<\/code>, this is by default a <code>std::<wbr \/>terminate<\/code>. If you explicitly enable a throwing destructor, then what happens next depends on why the destructor is running. If it&#8217;s running due to executing leaving the block normally, then the exception propagates outward. But if it&#8217;s running due to unwinding as a result of some other exception, then that&#8217;s a <code>std::<wbr \/>terminate<\/code>.<\/p>\n<p>The dangerous parts are the first two cases, because those result in the exception being thrown (and possibly caught elsewhere) without the cleanup action ever taking place.<\/p>\n<p>WIL addresses this problem by merely saying that if an exception occurs during copying\/moving of the lambda, then the behavior is undefined.<\/p>\n<p>C++ has a <code>scope_<wbr \/>exit<\/code> that is <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/experimental\/scope_exit\/scope_exit.html\"> in the experimental stage<\/a>, and it addresses the problem a different way: If an exception occurs during the construction of the <code>capture<\/code>, then the lambda is called before propagating the exception. (It can&#8217;t do anything about exceptions during contruction of the lambda, and it also declares the behavior undefined if the lambda itself throws an exception.)<\/p>\n<p>In practice, the problems with exceptions on construction or copy are immaterial because the lambda typically captures all values by reference (<code>[&amp;]<\/code>), and those types of captures do not throw on construction or copy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>But maybe it&#8217;s not worth it.<\/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-112266","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>But maybe it&#8217;s not worth it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112266","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=112266"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112266\/revisions"}],"predecessor-version":[{"id":112267,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112266\/revisions\/112267"}],"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=112266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}