在C++/WinRT中创建敏捷版的Windows Runtime委托,第6部分

The Old New Thing (Raymond Chen) 工具

摘要

系列文章第6部分:在C++/WinRT中创建敏捷的Windows Runtime委托,修复std::unique_ptr自定义删除器构造函数可能引发的异常问题。

<p>当我们<a title="在C++/WinRT中创建敏捷版的Windows Runtime委托,第5部分" href="https://devblogs.microsoft.com/oldnewthing/20260724-00/?p=112562">修复了在正确线程上释放不可封送委托的问题</a>后,看起来我们已经完成了。</p> <p>但我们遗漏了一些东西。</p> <p>又一次。</p> <pre> if (d.try_as&lt;::INoMarshal&gt;()) { void* p; if constexpr (std::is_reference_v&lt;Delegate&gt;) { p = winrt::detach_abi(d); } else { winrt::copy_to_abi(d, p); } return [p = std::unique_ptr&lt;void, in_context_deleter&gt;(p), token = get_context_token()](auto&amp;&amp;...args) { if (token == get_context_token()) { std::remove_reference_t&lt;Delegate&gt; d; winrt::copy_from_abi(d, p.get()); d(std::forward&lt;decltype(args)&gt;(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } </pre> <p>第一部分获取原始ABI指针,要么通过移动(如果可能)从入站委托中移出,要么从入站委托中复制。引用计数由原始指针拥有。</p> <p>第二部分将原始ABI指针包装到带有我们自定义删除器的<code>std::<wbr />unique_ptr</code>中。现在unique指针拥有引用计数,自定义删除器将释放它。</p> <p>问题在于,自定义删除器的要求之一是:如果使用<code>unique_ptr(p)</code>构造函数,则自定义删除器在构造时不得抛出异常。</p> <blockquote class="q"> <p><b>[unique.ptr.single.ctor]</b></p> <pre>constexpr explicit unique_ptr(type_identity_t&lt;pointer&gt; p) noexcept; </pre> <p>约束条件:<code>is_<wbr />pointer_<wbr />v&lt;deleter_<wbr />type&gt;</code>为<code>false</code>,并且<code>is_<wbr />default_<wbr />constructible_<wbr />v&lt;deleter_<wbr />type&gt;</code>为<code>true</code>。</p> <p>前置条件:<code>D</code>满足Cpp17DefaultConstructible要求,并且<span style="border: solid 1px currentcolor;">构造不会抛出异常</span>。</p> </blockquote> <p>但我们的自定义删除器可能会在<code>CoGetObjectContext</code>失败时抛出异常。因此它不满足前置条件。</p> <p>我们可以通过使用接受显式删除器的构造函数来修复,该删除器可以移动构造存储的删除器。如果发生异常,则发生在参数创建期间,而不是在<code>unique_<wbr />ptr</code>构造函数内部。</p> <pre> if (d.try_as&lt;::INoMarshal&gt;()) { void* p; if constexpr (std::is_reference_v&lt;Delegate&gt;) { p = winrt::detach_abi(d); } else { winrt::copy_to_abi(d, p); } return [p = std::unique_ptr&lt;void, in_context_deleter&gt;(p, <span style="border: solid 1px currentcolor;">{}</span>), token = get_context_token()](auto&amp;&amp;...args) { if (token == get_context_token()) { std::remove_reference_t&lt;Delegate&gt; d; winrt::copy_from_abi(d, p.get()); d(std::forward&lt;decltype(args)&gt;(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } </pre> <p>好了,现在我们应该完成了吧?</p> <p>不,仍然有问题。</p> <p>下次继续。</p> <p>本文<a href="https://devblogs.microsoft.com/oldnewthing/20260727-00/?p=112566">在C++/WinRT中创建敏捷版的Windows Runtime委托,第6部分</a>首次发表在<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>上。</p>
查看原文
查看缓存全文

缓存时间: 2026/07/28 06:24

# 在 C++/WinRT 中制作敏捷版本的 Windows 运行时委托,第 6 部分 - 《旧日新事》 来源:https://devblogs.microsoft.com/oldnewthing/20260727-00?p=112566 之前我们修复了在正确线程上释放不可封送委托的问题(https://devblogs.microsoft.com/oldnewthing/20260724-00/?p=112562),看起来已经大功告成了。但我们又遗漏了什么。又一次。 ```cpp if (d.try_as<::INoMarshal>()) { void* p; if constexpr (std::is_reference_v<T>) { p = winrt::detach_abi(d); } else { winrt::copy_to_abi(d, p); } return [p = std::unique_ptr<...>(p), token = get_context_token()](auto&&...args) { if (token == get_context_token()) { std::remove_reference_t<T> d; winrt::copy_from_abi(d, p.get()); d(std::forward<...>(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } ``` 第一部分获取一个原始 ABI 指针:如果可以的话,将其移出入站委托,否则从其复制出来。引用计数由原始指针拥有。 第二部分将原始 ABI 指针包装到带有我们自定义删除器的 `std::unique_ptr` 中。该 unique 指针现在拥有引用计数,自定义删除器将释放它。 问题在于,自定义删除器的一个要求是:如果你使用 `unique_ptr(p)` 构造函数,则自定义删除器在构造时不得抛出异常。 > **[unique.ptr.single.ctor]** > `constexpr explicit unique_ptr(type_identity_t<T> p) noexcept;` > 约束:`is_pointer_v<D>` 为 `false` 且 `is_default_constructible_v<D>` 为 `true`。 > 前置条件:`D` 满足 Cpp17DefaultConstructible 要求,且其构造不会抛出异常。 但我们的自定义删除器在 `CoGetObjectContext` 失败时可能会抛出异常,因此它不满足前置条件。我们可以通过使用接受显式删除器的构造函数来解决,该删除器可以移动构造到存储的删除器中。如果发生异常,它发生在参数创建期间,而不是在 `unique_ptr` 构造函数内部。 ```cpp if (d.try_as<::INoMarshal>()) { void* p; if constexpr (std::is_reference_v<T>) { p = winrt::detach_abi(d); } else { winrt::copy_to_abi(d, p); } return [p = std::unique_ptr<...>(p, {}), token = get_context_token()](auto&&...args) { if (token == get_context_token()) { std::remove_reference_t<T> d; winrt::copy_from_abi(d, p.get()); d(std::forward<...>(args)...); } else { throw winrt::hresult_error(CO_E_NOT_SUPPORTED); } }; } ``` 好了,现在总该完事了吧?不,还是有错。下次继续。 ### 分类 ### 主题 ## 作者 **Raymond Chen** Raymond 参与 Windows 演变已有 30 多年。2003 年,他创办了名为“The Old New Thing”的网站,其流行程度远超他最疯狂的想象——这一发展至今仍让他感到毛骨悚然。该网站催生了一本书,巧合的是书名也叫《The Old New Thing》(Addison Wesley, 2007)。他偶尔会出现在 Windows Dev Docs Twitter 账号上,讲述一些毫无实用信息的故事。

相似文章