通过提取函数的类型相关部分来减少C++模板膨胀,实践示例
摘要
本文解释了如何通过提取函数的类型相关部分来减少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<typename Lambda>
bool Widget::QueueToWorkerThread(Lambda&& lambda)
{
CreateWorkerThreadIfNeeded();
return m_dispatcherQueue.TryEnqueue(
std::forward<Lambda>(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& handler)
{
CreateWorkerThreadIfNeeded();
return m_dispatcherQueue.TryEnqueue(handler);
}
template<typename Lambda>
bool Widget::QueueToWorkerThread(Lambda&& lambda)
{
winrt::DispatcherQueueHandler handler(std::forward<Lambda>(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& 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++模板膨胀
本文介绍了通过将类型依赖代码提取到辅助对象中或使用基于span的方法来减少C++模板膨胀的技术,以提高代码效率和可维护性。
现代化一个25年的最小化C++单元测试框架(第二部分)
本文继续系列内容,讨论如何现代化一个25年的最小化C++单元测试框架,重点介绍使用内联变量和模块来解决文件特定计数器和头文件依赖的问题。
编写高效的C++代码
本文讨论了编写高效C++代码的技术,着重于面向数据设计和性能优化,适用于游戏和实时处理等应用。
在C++/WinRT中创建敏捷版的Windows Runtime委托,第6部分
系列文章第6部分:在C++/WinRT中创建敏捷的Windows Runtime委托,修复std::unique_ptr自定义删除器构造函数可能引发的异常问题。
在C++/WinRT中创建Windows Runtime委托的敏捷版本,第8部分
本文讨论了在C++/WinRT中创建敏捷委托时修复异常安全性问题,解决了未指定lambda捕获构造顺序导致的引用泄漏。