我已经多次看到创建自定义 streambufs 的说明: 您需要做的就是在std::basic_streambuf
的后代中正确实现overflow
、underflow
和pbackfail
,然后您可以创建一个使用它格式化数据的流。这三个例程定义了自定义流的"受控序列"。
但std::basic_streambuf
的保护成员名单中潜伏着其他怪物,即setg
和setp
。 它们为输入和输出设置缓冲区。 获取和设置数据的公共成员首先尝试访问这些区域,然后再跟踪受控序列。
对于几个不同的自定义流,如果流设置自己的获取/放置区域,则可能会出现麻烦。所以我希望这样的 streambufs 避免使用 get/put 区域,并始终使用overflow
、underflow
和pbackfail
,没有任何中间缓冲。
对于一个朴素的简化示例,如果您要包装另一个 streambuf,则underflow
的实现可能如下所示:
template <class C, class TR>
typename TR::int_type wrapping_streambuf<C, TR>::underflow()
{
return m_wrapped_streambuf->sgetc();
}
让包裹的 streambuf 处理所有肮脏的工作。 这是另一个计算行数的幼稚示例:
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;
}
对于此类流,没有有用的setg
实现,因为您无法获取包装缓冲区的内部获取区域。 对于tracked_streambuf
,强加获取/放置区域将使计数线与流的逻辑序列同步,变得不可能。
我认为答案是永远不要在后代类中称呼setg
或setp
。事实上,它们可能应该覆盖setg
、setp
、gbump
和pbump
来抛出异常。
查看<streambuf>
标题,我看到自定义流 如果我这样做,在我最喜欢的库中实现可能会以我想要的方式工作(有对空 gptr/pptr 的检查)。但这是一种保证吗?
std::basic_streambuf
的默认构造函数将定义 get 和 put 区域的六个指针设置为 null 指针值,因此默认情况下它不会"创建自己的 get/put 区域"。
函数setg
、setp
、gbump
和pbump
是受保护的成员,默认情况下不会被公共成员函数调用,因此您不必担心它们。当然,覆盖它们以引发异常也不错。
此外,没有中间缓冲区的自定义流缓冲区类也应重写uflow
函数,公共成员函数可以调用该函数来处理需要高级 get 指针值的溢出情况。默认情况下,其默认行为为(引自 [streambuf.virt.get]/16):
默认行为:调用
underflow()
。如果underflow()
返回traits::eof()
,则返回traits::eof()
。否则,返回traits::to_int_type(*gptr())
的值并递增输入序列的下一个指针的值。
因此,如果不重写此函数,则会导致通过 null 指针的间接寻址出现未定义的行为。