Search code examples
c++streambuf

Does `basic_streambuf` create its own get/put areas if you don't do it in a derived class implementation?


I've seen the instructions for creating custom streambufs many times: All you need to do is implement overflow, underflow, and pbackfail properly in a descendant of std::basic_streambuf and you can create a stream that formats data using it. Those three routines define your custom stream's 'controlled sequence'.

But there are other monsters lurking in the protected member list of std::basic_streambuf, namely setg and setp. These set up buffer areas for input and output. The public members that get and set data try to access these areas first before going after the controlled sequence.

For a couple of different custom streambufs there might be trouble if the stream sets up its own get/put areas. So I would like such streambufs to avoid using the get/put areas and always use overflow, underflow, and pbackfail without any intermediate buffering.

For a naive simplified example, if you're wrapping another streambuf the implementation of underflow might look like this:

template <class C, class TR>
typename TR::int_type wrapping_streambuf<C, TR>::underflow()
{
 return m_wrapped_streambuf->sgetc();
}

Let the wrapped streambuf handle all of the dirty work. Here's another naive example for counting lines:

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;
}

For such streams, there is no useful implementation of setg because you can't get at the wrapped buffer's internal get area. For tracked_streambuf, the imposition of get/put areas would make counting lines, in sync with the stream's logical sequence, impossible.

I think the answer is to never call setg or setp in the descendant classes. In fact, they probably should override setg, setp, gbump, and pbump to throw exceptions.

Looking at the <streambuf> header I see that a custom streambuf in my favorite library implementation might work the way I want (there are checks for null gptr/pptr) if I do this. But is it a guarantee?


Solution

  • The default constructor of std::basic_streambuf sets the six pointers that define the get and put areas to null pointer values, so it will not "create its own get/put area" by default.

    The functions setg, setp, gbump, and pbump are protected members and would not be called by public member functions by default, so you needn't worry about them. Of course, overriding them to throw exceptions is not bad.

    In addition, a custom stream buffer class without intermediate buffer shall also override the uflow function, which may be invoked by public member functions to handle overflow cases where the value of the get pointer is required to be advanced. By default, its default behavior is (quoted from [streambuf.virt.get]/16):

    Default behavior: Calls underflow(). If underflow() returns traits​::​eof(), returns traits​::​eof(). Otherwise, returns the value of traits​::​to_­int_­type(*gptr()) and increment the value of the next pointer for the input sequence.

    So if you do not override this function, it results in undefined behavior for indirection through a null pointer.