{"id":112566,"date":"2026-07-27T07:00:00","date_gmt":"2026-07-27T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112566"},"modified":"2026-07-27T12:38:09","modified_gmt":"2026-07-27T19:38:09","slug":"20260727-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260727-00\/?p=112566","title":{"rendered":"Making an agile version of a Windows Runtime delegate in C++\/WinRT, part 6"},"content":{"rendered":"<p>It looked like we were done when we <a title=\"Making an agile version of a Windows Runtime delegate in C++\/WinRT, part 5\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260724-00\/?p=112562\"> fixed the problem of releasing a non-marshalable delegate on the correct thread<\/a>.<\/p>\n<p>But we missed something.<\/p>\n<p>Again.<\/p>\n<pre>    if (d.try_as&lt;::INoMarshal&gt;()) {\r\n        void* p;\r\n        if constexpr (std::is_reference_v&lt;Delegate&gt;) {\r\n            p = winrt::detach_abi(d);\r\n        } else {\r\n            winrt::copy_to_abi(d, p);\r\n        }\r\n        return\r\n            [p = std::unique_ptr&lt;void, in_context_deleter&gt;(p),\r\n            token = get_context_token()](auto&amp;&amp;...args) {\r\n                if (token == get_context_token()) {\r\n                    std::remove_reference_t&lt;Delegate&gt; d;\r\n                    winrt::copy_from_abi(d, p.get());\r\n                    d(std::forward&lt;decltype(args)&gt;(args)...);\r\n                } else {\r\n                    throw winrt::hresult_error(CO_E_NOT_SUPPORTED);\r\n                }\r\n            };\r\n    }\r\n<\/pre>\n<p>The first part gets a raw ABI pointer, either by moving it out of the inbound delegate if we can, else by copying it from the inbound delegate. The reference count is owned by the raw pointer.<\/p>\n<p>The second part wraps the raw ABI pointer inside a <code>std::<wbr \/>unique_ptr<\/code> with our custom deleter. The unique pointer now owns the reference count, and the custom deleter will release it.<\/p>\n<p>The problem is that one of the requirements for a custom deleter is that if you use the <code>unique_ptr(p)<\/code> constructor, the custom deleter must not throw an exception at construction.<\/p>\n<blockquote class=\"q\">\n<p><b>[unique.ptr.single.ctor]<\/b><\/p>\n<pre>constexpr explicit unique_ptr(type_identity_t&lt;pointer&gt; p) noexcept;\r\n<\/pre>\n<p>Constraints: <code>is_<wbr \/>pointer_<wbr \/>v&lt;deleter_<wbr \/>type&gt;<\/code> is <code>false<\/code> and <code>is_<wbr \/>default_<wbr \/>constructible_<wbr \/>v&lt;deleter_<wbr \/>type&gt;<\/code> is <code>true<\/code>.<\/p>\n<p>Preconditions: <code>D<\/code> meets the Cpp17DefaultConstructible requirements, and that <span style=\"border: solid 1px currentcolor;\">construction does not throw an exception<\/span>.<\/p>\n<\/blockquote>\n<p>But our custom deleter could throw an exception if <code>Co\u00adGet\u00adObject\u00adContext<\/code> fails. So it doesn&#8217;t meet the preconditions.<\/p>\n<p>We can fix that by using the constructor that takes an explicit deleter from which the stored deleter can be move-constructed. If an exception occurs, it happens during the creation of the parameter and not inside the <code>unique_<wbr \/>ptr<\/code> constructor.<\/p>\n<pre>    if (d.try_as&lt;::INoMarshal&gt;()) {\r\n        void* p;\r\n        if constexpr (std::is_reference_v&lt;Delegate&gt;) {\r\n            p = winrt::detach_abi(d);\r\n        } else {\r\n            winrt::copy_to_abi(d, p);\r\n        }\r\n        return\r\n            [p = std::unique_ptr&lt;void, in_context_deleter&gt;(p, <span style=\"border: solid 1px currentcolor;\">{}<\/span>),\r\n            token = get_context_token()](auto&amp;&amp;...args) {\r\n                if (token == get_context_token()) {\r\n                    std::remove_reference_t&lt;Delegate&gt; d;\r\n                    winrt::copy_from_abi(d, p.get());\r\n                    d(std::forward&lt;decltype(args)&gt;(args)...);\r\n                } else {\r\n                    throw winrt::hresult_error(CO_E_NOT_SUPPORTED);\r\n                }\r\n            };\r\n    }\r\n<\/pre>\n<p>Okay, so now we&#8217;re done?<\/p>\n<p>Nope, still broken.<\/p>\n<p>More next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Exception-safety, the invisible bug.<\/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-112566","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Exception-safety, the invisible bug.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112566","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=112566"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112566\/revisions"}],"predecessor-version":[{"id":112567,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112566\/revisions\/112567"}],"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=112566"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112566"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112566"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}