基于POSIX文件描述符实现std::streambuf的方法与问题解答
问题原因
你的代码写入正常但读取失败,核心原因是对std::streambuf的虚函数调度逻辑理解有误:xsgetn不是输入流程的必走路径,operator>>调用的非虚函数snextc/sgetc/sbumpc,会先检查内部get区域指针,当get区为空时直接调用虚函数underflow()获取数据。你没有重写underflow,基类默认的underflow直接返回EOF,因此读取流程直接终止,根本不会走到你实现的xsgetn。
另外你写完文件后,文件描述符的偏移量停在文件末尾,就算underflow实现正确,直接调用read也会因为偏移在末尾读到0字节,同样返回EOF。
std::streambuf基类默认处理逻辑
基类已经封装了所有公共非虚接口的调度逻辑,不需要派生类重写非虚函数:
- 维护get区(读缓冲区)、put区(写缓冲区)的三组指针边界,自动做指针前移、边界检查
- 所有public读写接口(
sgetc/snextc/sputc/sputn等)内部自动转调对应虚函数,默认实现如下:xsgetn:循环调用sbumpc()逐字符读取,直到读满指定长度或碰到EOFxsputn:循环调用sputc()逐字符写入,直到写满指定长度或写入失败underflow():默认返回EOF,标识无数据可读uflow():默认调用underflow(),成功后前移读指针返回字符,失败返回EOFoverflow(int c):默认返回EOF,标识无法写入pbackfail(int c):默认返回EOF,标识不支持字符回退sync():默认返回0,不做任何刷盘动作seekoff()/seekpos():默认返回失败,不支持随机寻址
- 提供
setg()/setp()保护方法,供派生类设置自定义读写缓冲区的起止指针。
功能完备的streambuf必须重写的函数
根据支持的能力不同,需要重写的核心虚函数不同:
- 仅支持输出(配合
std::ostream):- 必选:重写
overflow(int c),处理单字符写入、缓冲区满后的刷盘逻辑,否则逐字符写入场景会直接失败。你之前仅重写xsputn能跑是测试场景刚好走了批量写路径,属于巧合,不是通用实现。 - 可选优化:重写
xsputn实现批量写入,减少虚函数调用开销;重写sync实现用户触发的刷盘逻辑。
- 必选:重写
- 仅支持输入(配合
std::istream):- 必选:重写
underflow(),从数据源读取数据填充读缓冲区,是所有读操作的核心入口。 - 可选优化:重写
xsgetn实现批量读取;需要支持字符回退(比如unget)就重写pbackfail;需要随机寻址就重写seekoff/seekpos。
- 必选:重写
- 支持双向读写:
- 同时实现上述输入、输出的必选虚函数,额外处理读写切换时的缓冲区刷新、文件偏移同步逻辑。
代码修复参考
修复两个核心问题:补充underflow实现、写完后重置文件偏移到开头,同时增加简单缓冲区减少系统调用次数:
#include <iostream> #include <streambuf> #include <string> #include <cassert> #include <cstring> #include <fcntl.h> #include <unistd.h> class fd_streambuf : public std::streambuf { int fd; char read_buf[1024]; char write_buf[1024]; public: fd_streambuf(int fileDescriptor) : fd(fileDescriptor) { setp(write_buf, write_buf + sizeof(write_buf)); } ~fd_streambuf() override { sync(); close(fd); } protected: int overflow(int c) override { if (sync() == -1) return EOF; if (c != EOF) { *pptr() = c; pbump(1); } return c; } int sync() override { ssize_t len = pptr() - pbase(); if (len > 0) { ssize_t written = write(fd, pbase(), len); if (written != len) return -1; setp(write_buf, write_buf + sizeof(write_buf)); } return fsync(fd); } std::streamsize xsputn(const char* s, std::streamsize n) override { if (sync() == -1) return 0; return write(fd, s, n); } int underflow() override { ssize_t n = read(fd, read_buf, sizeof(read_buf)); if (n <= 0) return EOF; setg(read_buf, read_buf, read_buf + n); return *gptr(); } std::streamsize xsgetn(char* s, std::streamsize n) override { std::streamsize total = 0; if (gptr() != egptr()) { std::streamsize avail = egptr() - gptr(); std::streamsize copy = std::min(avail, n); memcpy(s, gptr(), copy); gbump(copy); s += copy; total += copy; n -= copy; } if (n > sizeof(read_buf)/2) { ssize_t r = read(fd, s, n); if (r > 0) total += r; return total; } if (n > 0 && underflow() != EOF) { std::streamsize avail = egptr() - gptr(); std::streamsize copy = std::min(avail, n); memcpy(s, gptr(), copy); gbump(copy); total += copy; } return total; } }; int main() { int fd = open("/tmp/test.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); fd_streambuf fd_buf(fd); std::ostream os(&fd_buf); os << "Hello, world!" << std::endl; os.flush(); lseek(fd, 0, SEEK_SET); std::istream is(&fd_buf); std::string fileContent; is >> fileContent; assert(fileContent == "Hello,"); std::string rest; is >> rest; assert(rest == "world!"); return 0; }
内容的提问来源于stack exchange,提问作者Gian Laager
相关产品推荐
相关产品推荐

