siware.dev

Custom std::streambuf

C++ has stream classes that are useful for input and output operations on files, io devices and strings. For example, we can use std::ostream to output to std::stringstream. However, there’s no built-in way to pre-allocate output buffer of std::stringstream which means there might be multiple allocations call as we output more bytes to the stream. Luckily, stream exposes the buffer that we can inherit and override some of the methods.

Doing a full and proper implementation of custom std::streambuf is out of scope for this short article. I am just going to show a simple custom std::streambuf that pre-allocates a fixed capacity buffer used for stream out. We will use std::string to manage this custom string buffer and manipulate std::streambuf internal put pointers (so that tellp() will also work). The following protected members of std::streambuf can be used for manipulating put pointers (see https://www.cplusplus.com/reference/streambuf/streambuf/ for more complete description):

Here’s the code:

class FixedStreamBuf : public std::streambuf
{
    std::string& buffer;

public:
    FixedStreamBuf(std::string& buffer, size_t bufferMaxSize)
        : buffer(buffer)
    {
        buffer.resize(bufferMaxSize);
        setp(buffer.data(), buffer.data() + buffer.size());
    }

    ~FixedStreamBuf()
    {
        const size_t writtenSize = pptr() - pbase();
        buffer.resize(writtenSize);
    }

protected:
    std::streamsize xsputn(const char* s, std::streamsize n) override
    {
        std::memcpy(pptr(), s, n);
        pbump(n);
        return n;
    };

    int_type overflow(int_type ch) override
    {
        // Fixed stream buffer doesn't handle overflow because we don't want to
        throw std::runtime_error("FixedStreamBuf overflow!");
    }

    pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override
    {
        // Only output stream is supported
        if (which != std::ios_base::out)
            throw std::runtime_error("Not supported");

        if (dir == std::ios_base::cur)
            pbump(off);
        else if (dir == std::ios_base::end)
            setp(pbase(), epptr() + off, epptr());
        else if (dir == std::ios_base::beg)
            setp(pbase(), pbase() + off, epptr());
        return pptr() - pbase();
    }
};

Simple example on how to use this:

std::string streamData;

// write to streamData via std::ostream
{
    FixedStreamBuf streamBuf(streamData, 1024);
    std::ostream ostr(&streamBuf);
    ostr << "Hello World!";
}

// do something with streamData
doSomething(streamData);

References