C++如何实现函数结果的自动缓存 _ 备忘录模式与map应用【实战】

浅浩君_7960

浅浩君_7960

2026-04-13

661人浏览

原创

缓存函数结果前需评估必要性:若函数执行快、参数组合少或调用频次低,缓存反而因哈希、内存、跳转开销而拖慢程序;嵌入式或高频循环中cache hit rate低于60%应警惕。

c++如何实现函数结果的自动缓存 _ 备忘录模式与map应用【实战】

缓存函数结果前,先确认是否真需要

直接上 std::map 或 std::unordered_map 做备忘录,很容易;但很多场景下反而拖慢程序。比如函数本身执行很快(如几个整数运算)、输入参数组合极少、或调用频次极低——这时缓存带来的哈希计算、内存分配、查找跳转开销,可能比重复计算还高。尤其在嵌入式或高频循环中,cache hit rate 低于 60% 就该警惕。

实操建议:

  • 先用 std::chrono 粗略测下原函数耗时,若平均
  • 记录实际调用参数分布(可临时加 std::cout ),确认是否存在重复模式
  • 避免对带副作用的函数缓存(如含 rand()、time(nullptr)、IO 的函数),否则结果会“假一致”

用 std::unordered_map 实现线程不安全但高效的缓存

这是最常用、也最容易写出 bug 的方式。核心是把参数序列化为唯一 key,映射到返回值。C++ 没有内置元组哈希(C++17 起 std::hash<:tuple>></:tuple> 才可用),所以常见做法是手动拼接或用 std::hash 组合:

template <typename r typename... args>
class memoize {
    std::unordered_map<:size_t r> cache_;
    std::function<r> func_;
<p>public:
memoize(std::function<r> f) : func_(f) {}</r></p>
<pre class="brush:php;toolbar:false;">R operator()(Args... args) {
    // 简单哈希:用 std::hash 对每个参数 hash 后异或(注意:异或不保序,但对缓存够用)
    auto key = hash_args(args...);
    auto it = cache_.find(key);
    if (it != cache_.end()) return it->second;
    R result = func_(args...);
    cache_[key] = result;
    return result;
}

private: template std::size_t hash_arg(const T& t) { return std::hash{}(t); }

std::size_t hash_args() { return 0; }
template <typename t typename... rest>
std::size_t hash_args(const T& t, const Rest&... rest) {
    return hash_arg(t) ^ (hash_args(rest...) <p>};</p></typename>

注意点:

  • std::unordered_map::operator[] 会在 key 不存在时默认构造 value,对非默认可构造类型(如 std::unique_ptr)会编译失败,此时改用 insert() 或 try_emplace()
  • 上面的哈希组合只是示意,^ 和左移容易碰撞,生产环境建议用 boost::hash_combine 或自定义哈希器
  • 没加锁,多线程并发调用同一 memoize 实例会崩溃——别指望“读多写少”就省锁,unordered_map::find + insert 不是原子操作

线程安全版:读写锁 or std::shared_mutex(C++17)

如果必须多线程共享一个缓存实例,std::shared_mutex 是比 std::mutex 更合适的选择:多个读可以并行,仅写入(即首次计算+插入)需独占。

关键修改点:

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
  • 成员变量加 mutable std::shared_mutex mtx_;(mutable 允许 const 成员函数里加锁)
  • operator() 改为 const,并在查找时用 std::shared_lock,插入时用 std::unique_lock
  • 避免“先 unlock 再 insert”导致的重复计算竞争:必须在持有 unique_lock 期间完成查找+插入(即用 try_emplace 或 insert 的返回值判断)

示例片段:

R operator()(Args... args) const {
    auto key = hash_args(args...);
    std::shared_lock<:shared_mutex> lock(mtx_);
    auto it = cache_.find(key);
    if (it != cache_.end()) return it->second;
<pre class="brush:php;toolbar:false;">// 释放读锁,获取写锁
lock.unlock();
std::unique_lock<:shared_mutex> wlock(mtx_);
// 再查一次:防止其他线程已插入
it = cache_.find(key);
if (it != cache_.end()) return it->second;

R result = func_(args...);
cache_.emplace(key, result); // emplace 不会覆盖已有 key
return result;</:shared_mutex>

}

更轻量的替代:局部 static map + 函数内联

如果函数是纯计算、参数类型固定、且只在单个函数内调用,比泛型 memoize 类更简单可靠:

int expensive_calc(int a, int b) {
    static std::unordered_map<:pair int>, int> cache;
    static std::mutex mtx;
<pre class="brush:php;toolbar:false;">auto key = std::make_pair(a, b);
std::lock_guard<:mutex> lock(mtx);
auto it = cache.find(key);
if (it != cache.end()) return it->second;

int result = /* real computation */;
cache[key] = result;
return result;</:mutex>

}

优点是无模板膨胀、生命周期明确;缺点是无法复用、静态变量初始化存在 static initialization order fiasco 风险(不过 unordered_map 是平凡类型,C++11 起保证零初始化)。真正要注意的是:别在 constexpr 上下文或 DLL 导出函数里用它——静态局部变量的地址在不同模块可能不一致。

缓存失效永远比缓存本身更难处理。没设计清除逻辑的备忘录,三个月后大概率变成内存泄漏源。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2218

7

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

2026.03.13

371

19

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4238

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1169

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

403

30

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2026.01.21

559

24

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

2026.02.06

389

20

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

2026.03.16

233

22

Java 多线程与并发编程实战
Java 多线程与并发编程实战

深入讲解 Java 并发编程体系,涵盖 Thread / Runnable / Callable 线程创建方式、线程生命周期与状态转换、synchronized 同步锁与 Lock/ReentrantLock 显式锁、volatile 可见性保证、ThreadPool 线程池配置与调优(核心参数/拒绝策略)、JUC 并发工具类(CountDownLatch/CyclicBarrier/Semaphore/ConcurrentHashMa

2026.04.17

349

31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习