std::views中transform后使用filter如何避免额外性能开销?
C++20 std::views transform后接filter重复执行的规避方案
以下是不同场景下可选用的可行方案:
方案1:C++23 原生std::views::cache1(最优推荐)
这是标准库针对这类惰性求值重复计算问题提供的专属解决方案,cache1适配器会缓存上游最近一次计算的transform结果,filter迭代器的operator++查找和operator*取值都会复用同一份缓存,不会触发重复执行。
使用示例:
// 注意cache1要放在transform之后、filter之前 auto result = input_range | std::views::transform(expensive_transform_func) | std::views::cache1 | std::views::filter(filter_predicate);
该方案无额外内存开销,代码改动最小,仅要求编译器支持C++23标准。
方案2:C++20 手动持久化transform结果
如果只能使用C++20标准,可以先把transform的所有计算结果导出到中间容器,再基于已计算完成的容器做filter操作,保证transform逻辑仅执行一轮。
使用示例:
// 先把所有transform结果存到vector,完成全量计算 auto transformed_vec = input_range | std::views::transform(expensive_transform_func) | std::ranges::to<std::vector>(); // 再对已计算的结果做filter auto result = transformed_vec | std::views::filter(filter_predicate);
该方案的缺点是需要申请内存存储全量transform结果,适合transform计算代价远高于内存占用开销的场景。
方案3:C++20 合并transform与filter逻辑
不需要额外存储全量结果,也不需要高版本标准支持,通过optional打包变换+判断逻辑,规避重复执行:
auto result = input_range // 单次执行transform,同时做filter判断,符合条件就返回带值的optional,否则返回空 | std::views::transform([=](auto&& input_val) { auto transformed_val = expensive_transform_func(input_val); return filter_predicate(transformed_val) ? std::optional(std::move(transformed_val)) : std::nullopt; }) // 过滤掉空optional | std::views::filter([](auto&& opt) { return opt.has_value(); }) // 取出optional中存储的transform结果 | std::views::transform([](auto&& opt) { return *std::forward<decltype(opt)>(opt); });
该方案兼顾C++20兼容性和无全量内存开销的需求,仅代码写法稍繁琐。
额外注意事项
- 标准要求view的变换函数必须为无副作用的纯函数,本次重复执行的问题本质上是有副作用的transform逻辑暴露了惰性求值的实现细节,符合标准定义的行为,不属于标准库bug。
- 若使用range-v3第三方范围库,C20环境下也可直接使用range-v3提供的
views::cache1,功能与C23标准版本一致。
内容的提问来源于stack exchange,提问作者Elliott
相关产品推荐
相关产品推荐

