通过提取函数的类型相关部分来减少C++模板膨胀,实践示例

The Old New Thing (Raymond Chen) 工具

摘要

本文解释了如何通过提取函数的类型相关部分来减少C++模板膨胀,并使用Windows DispatcherQueueHandler的实践示例来去除代码模板化。

<p>不久前,我们观察到<a title="关于将可调用对象包装在仅以相同参数调用它的 lambda 中" href="https://devblogs.microsoft.com/oldnewthing/20260819-00/?p=112624">通常没有必要将可调用对象包装在 lambda 中</a>,最近又观察到<a title="通过提取函数的类型相关部分来减少C++模板膨胀" href="https://devblogs.microsoft.com/oldnewthing/20260820-00/?p=112629">我们可以应用减少C++模板膨胀的原则</a>来进一步简化函数。</p> <p>为了回顾一下,这是我们上次的代码:</p> <pre>template&lt;typename Lambda&gt; bool Widget::QueueToWorkerThread(Lambda&amp;&amp; lambda) { CreateWorkerThreadIfNeeded(); return m_dispatcherQueue.TryEnqueue( std::forward&lt;Lambda&gt;(lambda)); } </pre> <p>正如我之前指出的,lambda 对于模板函数来说是最坏的情况,因为每个 lambda 都是唯一的类型。每次调用它,都会强制生成一个新的函数。</p> <p>但我们可以将 lambda 提取出来,传递给一个工作函数。在这种情况下,我们只使用 lambda 来构造一个 DispatcherQueueHandler,因此可以预先构造 DispatcherQueueHandler,并将其用作共享类型。</p> <pre>namespace winrt { using namespace winrt::Windows::System; } bool Widget::QueueToWorkerThreadWorker( winrt::DispatcherQueueHandler const&amp; handler) { CreateWorkerThreadIfNeeded(); return m_dispatcherQueue.TryEnqueue(handler); } template&lt;typename Lambda&gt; bool Widget::QueueToWorkerThread(Lambda&amp;&amp; lambda) { winrt::DispatcherQueueHandler handler(std::forward&lt;Lambda&gt;(lambda)); return QueueToWorkerThreadWorker(handler); } </pre> <p>我们的工作函数接受共享类型 DispatcherQueueHandler,而主函数将 lambda 转换为共享类型,然后调用非模板化的工作函数。</p> <p>操作顺序改变了,但无论我们是否构造 DispatcherQueueHandler 并不重要。从技术上讲,如果 CreateWorkerThreadIfNeeded() 抛出异常,lambda 的右值引用将处于已移动状态,但这些 lambda 通常是在运行时创建并丢弃的,所以调用者并不关心它是否在错误中幸存。(如果构造 DispatcherQueueHandler 抛出异常,意味着 CreateWorkerThreadIfNeeded() 完全没有被调用,根据我们对函数的理解,这也不会成为问题。这甚至意味着我们不会费心创建工作线程。)</p> <p>但是,等等,我们可以更进一步。</p> <p>我们可以在函数参数中直接将 lambda 转换为 DispatcherQueueHandler!</p> <pre>bool Widget::QueueToWorkerThread( winrt::DispatcherQueueHandler const&amp; handler) { CreateWorkerThreadIfNeeded(); return m_dispatcherQueue.TryEnqueue(handler); } </pre> <p>当调用者传递一个 lambda 时,从 lambda 到 DispatcherQueueHandler 的转换构造函数在调用点生效,因此它已经以共享类型 DispatcherQueueHandler 的形式到达 QueueToWorkerThread 函数。</p> <p>太好了,我们能够完全去除函数的模板化。</p> <p>本文最初发布于 <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/08/21 15:35

# 通过提取函数的类型相关部分来减少 C++ 模板膨胀,实践解析 - The Old New Thing 来源:https://devblogs.microsoft.com/oldnewthing/20260821-00?p=112632 不久前,我们注意到通常没有必要将可调用对象包装在 lambda 表达式中 (https://devblogs.microsoft.com/oldnewthing/20260819-00/?p=112624),最近我们又观察到,可以运用减少 C++ 模板膨胀的原则 (https://devblogs.microsoft.com/oldnewthing/20260820-00/?p=112629) 来进一步简化函数。 为了回顾,这是我们上次停下的地方: ```cpp template <typename Lambda> bool Widget::QueueToWorkerThread(Lambda&& lambda) { CreateWorkerThreadIfNeeded(); return m_dispatcherQueue.TryEnqueue( std::forward(lambda)); } ``` 正如我之前指出的,lambda 表达式对于模板函数来说有点像是最坏的情况,因为每个 lambda 都是唯一的类型。每次调用它,都会强制生成一个新的函数。 但是,我们可以将 lambda 从函数体中提取出来,传递给一个工作函数。在这个例子中,我们使用 lambda 唯一要做的事情就是用它来构造一个 `DispatcherQueueHandler`。因此,我们可以预先构造 `DispatcherQueueHandler`,并使用它作为通用类型。 ```cpp namespace winrt { using namespace winrt::Windows::System; } bool Widget::QueueToWorkerThreadWorker( winrt::DispatcherQueueHandler const& handler) { CreateWorkerThreadIfNeeded(); return m_dispatcherQueue.TryEnqueue(handler); } template <typename Lambda> bool Widget::QueueToWorkerThread(Lambda&& lambda) { winrt::DispatcherQueueHandler handler(std::forward(lambda)); return QueueToWorkerThreadWorker(handler); } ``` 我们的工作函数接受共享类型 `DispatcherQueueHandler`,而主函数将 lambda 转换为共享类型,然后调用非模板化的工作函数。 操作顺序发生了变化,但是先构造 `DispatcherQueueHandler` 还是后构造并不重要。从技术上讲,这有细微差别,因为如果 `CreateWorkerThreadIfNeeded()` 抛出异常,lambda 的右值引用将处于已移动的状态。但这些 lambda 通常是临时创建并丢弃的,所以调用者并不关心它是否能在错误中存活下来。 (同样从技术上讲,如果构造 `DispatcherQueueHandler` 抛出异常,那么 `CreateWorkerThreadIfNeeded()` 根本不会被调用。根据我们目前看到的函数逻辑,这也不是问题。这意味着我们甚至不需要费心创建工作线程。) 但是,等等,我们可以更进一步。我们可以直接在函数参数中将 lambda 转换为 `DispatcherQueueHandler`! ```cpp bool Widget::QueueToWorkerThread( winrt::DispatcherQueueHandler const& handler) { CreateWorkerThreadIfNeeded(); return m_dispatcherQueue.TryEnqueue(handler); } ``` 当调用者传递一个 lambda 时,从 lambda 到 `DispatcherQueueHandler` 的转换构造函数会在调用点生效。因此,它在到达 `QueueToWorkerThread` 函数时,已经是我们通用类型 `DispatcherQueueHandler` 的形式了。 太棒了,我们完全成功地让这个函数去模板化了。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与 Windows 的演进已超过 30 年。2003 年,他创建了一个名为 "The Old New Thing" 的网站,其受欢迎程度远超他的想象,这至今仍让他感到不安。该网站催生了一本书,巧合的是也叫《The Old New Thing》(Addison Wesley, 2007)。他偶尔会出现在 Windows 开发者文档的 Twitter 账号上,讲述一些不传达任何有用信息的故事。

相似文章

编写高效的C++代码

Hacker News Top

本文讨论了编写高效C++代码的技术,着重于面向数据设计和性能优化,适用于游戏和实时处理等应用。