Dynamic Buffers
This section introduces dynamic buffers—growable storage that adapts to data flow between producers and consumers.
Prerequisites
-
Completed Buffer Algorithms
-
Understanding of buffer sequences and copying
The Producer/Consumer Model
Dynamic buffers serve as intermediate storage between a producer (typically network I/O) and a consumer (your application code).
The flow:
-
Producer writes data into the buffer
-
Buffer grows as needed to accommodate data
-
Consumer reads and processes data
-
Buffer releases consumed data
This model decouples production rate from consumption rate—the buffer absorbs variations.
The DynamicBuffer Concept
template<typename T>
concept DynamicBuffer = requires(T& t, std::size_t n) {
// Producer side
{ t.prepare(n) } -> MutableBufferSequence;
{ t.commit(n) };
// Consumer side
{ t.data() } -> ConstBufferSequence;
{ t.consume(n) };
// Capacity
{ t.size() } -> std::same_as<std::size_t>;
{ t.max_size() } -> std::same_as<std::size_t>;
{ t.capacity() } -> std::same_as<std::size_t>;
};
Producer Interface
prepare(n)
Returns mutable buffer space for writing up to n bytes:
auto buffers = dynamic_buf.prepare(1024); // Space for up to 1024 bytes
The returned space may be larger than requested. The data is not yet part of the readable sequence.
Consumer Interface
data()
Returns the readable data as a const buffer sequence:
auto readable = dynamic_buf.data();
// Process readable bytes
consume(n)
Removes n bytes from the front of readable data:
dynamic_buf.consume(processed_bytes);
// Those bytes are no longer in data()
Typical Consumer Pattern
void process_buffer(DynamicBuffer auto& buffer)
{
auto data = buffer.data();
while (buffer_size(data) >= message_header_size)
{
auto msg_size = parse_header(data);
if (buffer_size(data) < msg_size)
break; // Need more data
process_message(data, msg_size);
buffer.consume(msg_size);
data = buffer.data(); // Refresh after consume
}
}
Capacity Management
size()-
Current number of readable bytes (the length of
data()). max_size()-
Maximum allowed size. Attempts to grow beyond this throw or fail.
capacity()-
Current allocated capacity. May be larger than
size().
DynamicBufferParam
When passing dynamic buffers to coroutines, use DynamicBufferParam for safe parameter handling:
template<typename DB>
concept DynamicBufferParam = DynamicBuffer<std::remove_reference_t<DB>>;
template<DynamicBufferParam Buf>
task<std::size_t> read_until(Stream& stream, Buf&& buffer, char delimiter);
This concept ensures proper handling of lvalues and rvalues, preventing dangling references across suspension points.
Provided Implementations
flat_dynamic_buffer
Linear storage with single-buffer sequences:
#include <boost/capy/buffers/flat_dynamic_buffer.hpp>
flat_dynamic_buffer buffer;
buffer.prepare(1024);
// ... write data ...
buffer.commit(n);
// data() returns a single const_buffer
Advantages:
-
Contiguous memory—good for parsing that needs contiguous data
-
Cache-friendly
Disadvantages:
-
May require copying when buffer wraps or grows
circular_dynamic_buffer
Ring buffer implementation:
#include <boost/capy/buffers/circular_dynamic_buffer.hpp>
circular_dynamic_buffer<1024> buffer; // Fixed capacity
Advantages:
-
No copying on wrap—head/tail pointers move
-
Fixed memory footprint
Disadvantages:
-
data()may return two buffers (wrapped around end) -
Fixed capacity
Example: Line-Based Protocol
task<std::string> read_line(Stream& stream)
{
flat_dynamic_buffer buffer;
while (true)
{
// Prepare space and read
auto space = buffer.prepare(256);
auto [ec, n] = co_await stream.read_some(space);
if (ec.failed())
throw std::system_error(ec);
buffer.commit(n);
// Search for newline in readable data
auto data = buffer.data();
std::string_view sv(
static_cast<char const*>(data.data()), data.size());
auto pos = sv.find('\n');
if (pos != std::string_view::npos)
{
std::string line(sv.substr(0, pos));
buffer.consume(pos + 1); // Include newline
co_return line;
}
}
}
Reference
| Header | Description |
|---|---|
|
DynamicBuffer concept definition |
|
Linear dynamic buffer |
|
Ring buffer implementation |
|
Vector-backed adapter |
|
String-backed adapter |
You have now learned about dynamic buffers for producer/consumer patterns. This completes the Buffer Sequences section. Continue to Stream Concepts to learn about Capy’s stream abstractions.