Circular Buffer - rFronteddu/general_wiki GitHub Wiki

Imagine your buffer as a ring of fixed-size memory slots. You don’t move data around β€” you just move two pointers (indexes):

  • head_ β†’ where you write new bytes
  • tail_ β†’ where you read old bytes
Capacity = 8

[ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ]
          ↑              ↑
         tail_           head_
  • As you write data, head_ advances.
  • As you consume (read) data, tail_ advances.

When either reaches the end, it wraps around to 0.

After writing 3 bytes:

[ A ][ B ][ C ][   ][   ][   ][   ][   ]
  ↑              ↑
 tail_           head_

After reading 2 bytes:

[   ][   ][ C ][   ][   ][   ][   ][   ]
          ↑              ↑
         tail_           head_

When head_ == tail_, it can mean:

  • Buffer is empty (size_ == 0)
  • Buffer is full (size_ == capacity)

So we track the current size_ to disambiguate.

#include <stdio>
#include <vector>
#include <span>

class circular_buffer
{
    sdt::vector<uint8_t> buffer_;
    size_t head_ = 0; // write position
    size_t tail_ = 0; // read position
    size_t size_ = 0; // bytes currently stored

public:
    explicit circular_buffer(size_t capacity) : buffer_(capacity) {}
    
    size_t  write(const uint8_t * data, size_t n) {
        size_t written = 0;
        while (written < n && size_ < buffer_.size()) {
            buffer_[head] = data[written++];
            head_ = (head_ + 1) % buffer_.size();
            size_++;
        }
        return written;
    }

    std::span<uint8_t> contiguous_span(void) {
        if (tail_ + size_ <= buffer_.size()) {
            return {buffer_.data() + tail_, size_};
        } else {
            return {buffer_.data() + tail_, buffer_.size() - tail_};
        }
    }

    void consume(size_t n) {
        assert(n <= size_);
        tail_ = (tail_ + n) % buffer_.size();
        size_ -= n;
    }

    size_t available() const { return size_; }
    size_t capacity() const { return buffer_.size(); }
}
⚠️ **GitHub.com Fallback** ⚠️