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++98The 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:
using std::streambuf = std::basic_streambuf<char>; // C++98
using std::wstreambuf = std::basic_streambuf<wchar_t>; // C++98The concrete subclasses bundled with the standard library are:
std::filebuf/std::wfilebufβ backsfstreamobjects; defined in<fstream>std::stringbuf/std::wstringbufβ backsstringstreamobjects; 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:
| Area | Begin | Current | End |
|---|---|---|---|
| Get | eback() | gptr() | egptr() |
| Put | pbase() | 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
#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:
#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):
#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:
#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:
#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_streambufimplementation backed by a file descriptorstd::stringbufβbasic_streambufimplementation backed by astd::stringstd::istreambuf_iterator/std::ostreambuf_iteratorβ raw iterator access to stream buffer contentsstd::osyncstreamβ C++20 synchronized output stream wrapper for concurrent usestd::ios_base::sync_with_stdioβ controls C / C++ I/O synchronization