若未在派生类中实现,std::basic_streambuf是否会自行创建get/put区域?
自定义
std::basic_streambuf:跳过get/put缓冲区的标准合规性 首先直接给你结论:完全没问题,C++标准明确支持这种做法——你可以彻底绕过get/put缓冲区,只靠overflow()、underflow()和pbackfail()实现自定义streambuf,甚至可以重写setg()、setp()这类函数来阻止外部代码使用缓冲区。下面我来详细解释原因和实现细节:
1. 标准对streambuf工作流程的定义
C++标准规定了streambuf的操作优先级:
- 当流要读取数据时,先检查get缓冲区:如果
gptr()还没到egptr(),直接从缓冲区拿数据;如果缓冲区空了,就调用underflow()从受控序列拉取数据; - 写操作同理:先看put缓冲区有没有空间,没有就调用
overflow()把数据刷到受控序列; - 回退操作如果缓冲区满足不了(比如已经退到缓冲区开头),就调用
pbackfail()。
重点来了:如果get/put缓冲区的三个指针都相等(也就是eback() == gptr() == egptr()或者pbase() == pptr() == epptr()),流会直接跳过缓冲区检查,立刻调用对应的underflow()/overflow()。这不是某个编译器的特殊实现,是标准强制要求的行为。
2. 为什么要跳过get/put缓冲区?
像你提到的两种场景,用缓冲区反而会出问题:
- 包装其他streambuf:被包装的streambuf内部缓冲区是私有的,我们没法同步它的状态。要是自己搞个get缓冲区,很容易出现数据不同步,比如被包装的streambuf已经读了数据,但我们的缓冲区还存着旧数据,结果就是重复读取或者丢数据;
- 行计数这类跟踪流:如果用了get缓冲区,流会一次性把一堆字符读到缓冲区里,
uflow()只会在缓冲区耗完的时候才被调用,这时候你根本没法准确统计每一个换行符出现的时机,行数肯定算错。
3. 具体怎么实现?
你的思路完全正确:
- 别碰
setg()/setp():让get/put缓冲区保持初始的空状态就行,不用手动设置; - 重写
setg()、setp()、gbump()、pbump():让这些函数抛出异常(比如std::logic_error),防止别人不小心调用这些函数破坏你的streambuf逻辑。
比如你给的包装streambuf的underflow()实现就很规范:
template <class C, class TR> typename TR::int_type wrapping_streambuf<C, TR>::underflow() { return m_wrapped_streambuf->sgetc(); }
还有行计数的uflow():
template <class C, class TR> typename TR::int_type tracking_streambuf<C, TR>::uflow() { auto rv = m_wrapped_streambuf->sbumpc(); if (rv == (TR::int_type)'\n') ++m_input_line_count; return rv; }
这些实现完全绕开了缓冲区,直接和受控序列交互,完全符合标准。
4. 关于标准合规性的确认
你不用担心不同编译器的兼容性问题——所有符合C++标准的库都必须遵守"缓冲区空时调用underflow/overflow"的规则。另外,重写setg()/setp()这类protected成员是标准允许的,这些函数本来就是为了让派生类定制行为而设计的。抛出异常也是合法的,只要你在文档里说明这个streambuf不支持缓冲区操作就行。
内容的提问来源于stack exchange,提问作者Spencer
相关产品推荐
相关产品推荐

