Skip to content
C++
Library
since C++98
Advanced

std::streambuf

Abstract base class for C++ stream buffers, mediating raw character I/O between higher-level streams and the underlying data device.

std::basic_streambufsince C++98

The abstract intermediary between high-level stream objects and raw character sources or sinks, managing get and put buffer areas and defining a virtual interface for derived classes to specialize.

Overview

std::basic_streambuf<CharT, Traits> is the backbone of the C++ I/O architecture. Every stream object β€” std::istream, std::ostream, std::fstream, std::stringstream β€” delegates actual character movement to a basic_streambuf subclass. Two aliases cover the common cases:

cpp
using std::streambuf  = std::basic_streambuf<char>;    // C++98
using std::wstreambuf = std::basic_streambuf<wchar_t>; // C++98

The concrete subclasses bundled with the standard library are:

  • std::filebuf / std::wfilebuf β€” backs fstream objects; defined in <fstream>
  • std::stringbuf / std::wstringbuf β€” backs stringstream objects; defined in <sstream>

basic_streambuf manages two contiguous character arrays called the get area (input side) and the put area (output side). Pointers into these arrays are accessible via protected member functions:

AreaBeginCurrentEnd
Geteback()gptr()egptr()
Putpbase()pptr()epptr()

When a read exhausts the get area, the stream machinery calls underflow(). When a write fills the put area, it calls overflow(). Subclassing basic_streambuf means overriding these virtuals; everything else follows. This two-pointer model allows the buffer to track available-but-unread characters without copying them, which is fundamental to the performance of stream I/O.

The public API uses pub-prefixed forwarding functions (pubsync(), pubseekoff(), etc.) that call the protected virtuals. Never call the virtual functions directly from user code β€” always go through the public interface or through a stream object.

Syntax

cpp
#include <streambuf>

template<class CharT, class Traits = std::char_traits<CharT>>
class basic_streambuf {
public:
    // Locales
    std::locale pubimbue(const std::locale&);
    std::locale getloc() const;

    // Positioning
    pos_type pubseekoff(off_type, std::ios_base::seekdir,
                        std::ios_base::openmode = std::ios_base::in | std::ios_base::out);
    pos_type pubseekpos(pos_type,
                        std::ios_base::openmode = std::ios_base::in | std::ios_base::out);
    int pubsync();

    // Get area
    std::streamsize in_avail();
    int_type snextc();
    int_type sbumpc();
    int_type sgetc();
    std::streamsize sgetn(char_type* s, std::streamsize n);

    // Putback
    int_type sputbackc(char_type c);
    int_type sungetc();

    // Put area
    int_type sputc(char_type c);
    std::streamsize sputn(const char_type* s, std::streamsize n);

protected:
    // Customization-point virtuals
    virtual int_type  underflow();            // fill get area; do NOT advance gptr
    virtual int_type  uflow();               // fill get area; advance gptr
    virtual int_type  overflow(int_type c);  // flush put area; write c
    virtual int       sync();                // flush to underlying device
    virtual pos_type  seekoff(off_type, std::ios_base::seekdir, std::ios_base::openmode);
    virtual pos_type  seekpos(pos_type, std::ios_base::openmode);
    virtual std::streamsize showmanyc();
    virtual std::streamsize xsgetn(char_type* s, std::streamsize n);
    virtual std::streamsize xsputn(const char_type* s, std::streamsize n);

    // Buffer setup
    void setg(char_type* gbeg, char_type* gcurr, char_type* gend);
    void setp(char_type* pbeg, char_type* pend);
};

Examples

Stream redirection via rdbuf()

Every stream exposes rdbuf() as both a getter and a setter. This is the standard mechanism for redirecting std::cout, capturing output in tests, or temporarily routing a stream to a file:

cpp
#include <fstream>
#include <iostream>
#include <sstream>

// Capture std::cout into a string β€” useful in unit tests
void demo_capture() {
    std::ostringstream oss;
    std::streambuf* saved = std::cout.rdbuf(oss.rdbuf());  // redirect

    std::cout << "captured line\n";

    std::cout.rdbuf(saved);  // always restore before oss goes out of scope
    std::string result = oss.str();  // "captured line\n"
}

// Redirect cout to a file for a block
void demo_file_redirect() {
    std::ofstream log("run.log");
    std::streambuf* saved = std::cout.rdbuf(log.rdbuf());

    std::cout << "this goes to run.log\n";

    std::cout.rdbuf(saved);
}

Failing to restore the original buffer leaves the stream with a dangling pointer when the replacement buffer is destroyed β€” a silent use-after-free.

Custom streambuf: fan-out (tee) output

Subclassing basic_streambuf lets you intercept or duplicate character traffic without touching the higher-level stream API. The minimal contract for an output-only buffer is to override overflow() (single character) and xsputn() (bulk write):

cpp
#include <streambuf>
#include <ostream>
#include <algorithm>

class tee_buf : public std::streambuf {
public:
    tee_buf(std::streambuf* a, std::streambuf* b) : a_(a), b_(b) {}

protected:
    int_type overflow(int_type c) override {
        if (traits_type::eq_int_type(c, traits_type::eof()))
            return traits_type::not_eof(c);  // propagate EOF signal without error
        char_type ch = traits_type::to_char_type(c);
        if (traits_type::eq_int_type(a_->sputc(ch), traits_type::eof()))
            return traits_type::eof();
        return b_->sputc(ch);  // return eof() only on failure
    }

    std::streamsize xsputn(const char_type* s, std::streamsize n) override {
        std::streamsize wa = a_->sputn(s, n);
        std::streamsize wb = b_->sputn(s, n);
        return std::min(wa, wb);
    }

private:
    std::streambuf* a_;
    std::streambuf* b_;
};

// Usage:
//   std::ofstream logfile("app.log");
//   tee_buf tee(std::cout.rdbuf(), logfile.rdbuf());
//   std::ostream both(&tee);
//   both << "Written to stdout and app.log simultaneously\n";

Custom input buffer over a memory region

An input-only buffer requires setting up the get area with setg(). underflow() is only needed when the data spans multiple refill cycles; for a fixed memory region it is unnecessary:

cpp
#include <streambuf>
#include <istream>

class mem_buf : public std::streambuf {
public:
    mem_buf(const char* data, std::size_t len) {
        // setg takes non-const pointers β€” the interface predates const-correctness
        // in this area; we never write through gptr for an input-only buffer.
        char* p = const_cast<char*>(data);
        setg(p, p, p + len);
    }
    // No underflow() override needed: the full range is set up at construction.
};

// Usage:
//   const char raw[] = "42 3.14 hello";
//   mem_buf mb(raw, sizeof(raw) - 1);
//   std::istream is(&mb);
//   int n; double d; std::string s;
//   is >> n >> d >> s;  // n=42, d=3.14, s="hello"

Efficient file-to-string via istreambuf_iterator

istreambuf_iterator<CharT> reads directly from a basic_streambuf without formatting overhead, bypassing locale facets and numeric parsing. It is significantly faster than operator>> for raw text ingestion:

cpp
#include <fstream>
#include <iterator>
#include <string>

std::string slurp(const std::string& path) {
    std::ifstream f(path, std::ios::binary);
    return {std::istreambuf_iterator<char>(f),
            std::istreambuf_iterator<char>{}};
}

The iterator-range constructor of std::string performs this in a single pass with no intermediate copies.

Best Practices

Override xsputn() and xsgetn() for any performance-sensitive buffer. The default implementations call overflow()/underflow() one character at a time. Bulk overrides can use std::memcpy or equivalent, yielding orders-of-magnitude better throughput on large payloads.

Keep sync() honest. Returning 0 signals success; returning -1 sets badbit on the owning stream. If your buffer wraps a file descriptor or socket, flush it here. A sync() that always returns 0 while dropping data is worse than a hard failure.

Make streambuf subclasses non-copyable unless you've carefully reasoned through pointer ownership. The internal gptr/pptr machinery makes naive copy semantics fragile. Mark copy constructor and copy-assignment = delete by default.

Tie streams when synchronizing related buffers. ios::tie() causes one stream to flush before another is used. std::cin and std::cout are tied by default β€” this is why prompts appear before the cursor. Untying them with std::cin.tie(nullptr) can improve throughput at the cost of ordering guarantees.

Prefer std::osyncstream (C++20) over custom mutex-wrapping buffers for concurrent output. It queues a full atomic write per flush, avoiding interleaved output without manual locking.

Common Pitfalls

Confusing underflow() with uflow(). underflow() must not advance gptr() β€” it replenishes the buffer and returns the next character without consuming it. uflow() replenishes and consumes. Overriding the wrong one produces off-by-one reads that are difficult to diagnose.

Returning eof() from overflow() on success. overflow() must return traits_type::not_eof(c) β€” typically the written character itself β€” on success, and traits_type::eof() only on failure. Returning eof() unconditionally breaks the stream silently; writes appear to succeed at the call site but no data is stored.

sync_with_stdio(false) and mixing C stdio. By default, C++ streams synchronize with C's FILE* buffers. sync_with_stdio(false) decouples them for better performance, but after that call any mix of printf/scanf with std::cout/std::cin produces undefined ordering. Pick one interface and stay with it.

Dangling streambuf* after stream or buffer destruction. If you assign a streambuf* to a stream with rdbuf() and then destroy the buffer object, the stream holds a dangling pointer. The stream destructor will call pubsync() through it. Always restore the original buffer before any replacement buffer's lifetime ends.

setg and setp require non-const pointers. The C++98 interface was not designed with const data in mind. When wrapping read-only memory, const_cast is the accepted workaround, guarded by the invariant that the buffer never writes through gptr() in input-only mode.

See Also

  • std::filebuf β€” basic_streambuf implementation backed by a file descriptor
  • std::stringbuf β€” basic_streambuf implementation backed by a std::string
  • std::istreambuf_iterator / std::ostreambuf_iterator β€” raw iterator access to stream buffer contents
  • std::osyncstream β€” C++20 synchronized output stream wrapper for concurrent use
  • std::ios_base::sync_with_stdio β€” controls C / C++ I/O synchronization