std::fstream 本身不支持回调,需继承 std::streambuf 并重写 underflow()、sputn() 等函数实现底层字节级拦截;回调应置于 overflow()/sputn() 或 underflow()/sgetn() 中,避免在非虚函数或 sync() 中误用,注意跨平台差异与线程安全。

std::fstream 本身不支持回调,得自己封装流缓冲区
标准 C++ 的 std::ifstream 和 std::ofstream 是被动的——你调它,它读写;它不会主动通知你“我刚读了 1024 字节”或“写入失败了”。想加回调,必须绕过高层流接口,接管底层的 std::streambuf。这不是加个 lambda 就能搞定的事,而是要继承 std::streambuf,重写 underflow()、overflow()、sputn() 等关键函数。
常见错误是试图在 operator>> 或 read() 后手动插回调——这只能捕获“调用时机”,无法感知底层实际传输字节、缓冲区刷新、甚至系统 write() 返回值。真正需要回调的场景(比如实时日志上传、加密流处理、进度上报),必须在缓冲区粒度上拦截。
- 别碰
std::iostream::rdbuf()返回的原生streambuf*,它是只读视图;要 new 一个自定义子类实例,再用rdbuf()替换 - 重写
overflow(int c)处理单字符写入,sputn(const char* s, std::streamsize n)处理批量写入——后者才是性能关键路径 - 读操作同理:
underflow()触发填充输入缓冲区时回调,sgetn()对应批量读取 - 记得在自定义
streambuf析构时,把原始streambuf*(如文件对应的filebuf)正确 delete 或移交所有权,否则资源泄漏
回调触发点选哪个函数,取决于你要观察什么
不是所有缓冲区函数都适合塞回调。选错会导致重复触发、漏触发,或者破坏流语义。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 想监控“每次系统级 write 调用”:在
sync()里触发回调——它对应flush()或缓冲区满时的底层同步,但注意:sync()不一定每次write()都调,且可能被忽略(比如std::ios_base::out | std::ios_base::app模式下行为不同) - 想精确知道“哪几个字节被送进系统调用”:必须在
overflow()和sputn()内部,在调用父类原始写逻辑前后插回调——例如先调callback("write", s, n),再调original_sputn(s, n) - 读场景同理:
underflow()适合通知“新一批数据已从磁盘加载进缓冲区”,而sgetn()更适合通知“用户代码实际取走了多少字节” - 避免在
xsgetn()/xsputn()这类非虚函数里动手——它们不是标准重写点,各编译器实现可能跳过
观察者模式怎么和 streambuf 安全绑定
不能让 streambuf 直接持有 std::function 并无条件调用——回调里如果又去操作同一个流(比如在写回调里调 flush()),极易引发递归重入或迭代器失效。
- 用弱引用管理观察者:存储
std::weak_ptr<observer></observer>,每次触发前lock()判断是否还存活,避免悬挂调用 - 回调函数签名建议固定为
void(std::string_view event, const void* data, size_t size, std::error_code ec),统一覆盖读/写/错误三类事件,比拆成多个回调更易维护 - 不要在回调里做耗时操作(如网络请求、磁盘写入)——
streambuf是同步阻塞路径,卡住会影响整个流行为;真要异步,回调内只发消息到队列,另起线程处理 - 注意多线程:同一个
streambuf实例被多个线程共用时(比如共享的std::ostream&),回调触发点必须加锁;更稳妥的做法是每个线程独占一个带回调的流实例
Windows 下 filebuf 的 sync() 行为和 Linux 不一致
这是容易被忽略的坑。在 Windows 上,std::filebuf::sync() 默认会调用 _commit()(即 FlushFileBuffers),强制落盘;Linux 下则只是 fflush(),不保证物理写入。如果你的回调依赖 sync() 触发来表示“数据已持久化”,那跨平台时逻辑就错了。
- 别假设
sync()= “已落盘”;真正需要落盘语义,应在回调里显式调用fsync()(Linux)或FlushFileBuffers()(Windows),并检查返回值 - 用
std::ofstream构造时传std::ios_base::unitbuf可让每次插入符后自动flush(),但它不改变sync()底层行为,只是增加调用频次 - 测试时务必在目标平台实测:用 Process Monitor(Windows)或
strace -e trace=write,fsync,fcntl(Linux)验证回调触发时刻与系统调用是否对齐
流缓冲区回调不是语法糖,是侵入式改造。最复杂的点不在怎么写,而在怎么收口——回调里能不能抛异常?析构时要不要等回调完成?这些边界没想清,上线后只会变成偶发崩溃或静默丢数据。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










