Circular Buffers

A circular buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. It is typically used to buffer data streams. It is well suited to embedded systems because of the fixed-size buffer (i.e. the buffer is not dynamically resized).

The following example uses two condition variables to allow the reader and writer to wait on each other:


template<class T>
class CircularBuffer
{
public:
    explicit CircularBuffer(int size) : buffer(size) {}

    size_t size() const { return buffer.size(); }

    void Write(const T& value)
    {
        std::unique_lock lock(mutex);
        notFull.wait(lock, [this]() { return count < size(); });
        buffer[writePos] = value;
        if (count == 0)
            notEmpty.notify_one();
        ++count;
        writePos = (writePos+1) % size();
    }

    void Read(T& value)
    {
        std::unique_lock lock(mutex);
        notEmpty.wait(lock, [this]() { return count > 0; });
        if (count == size())
            notFull.notify_one();
        --count;
        value = buffer[readPos];
        readPos = (readPos+1) % size();
    }

private:
    std::mutex mutex;
    std::condition_variable notEmpty;
    std::condition_variable notFull;
    std::vector<T> buffer;
    size_t readPos = 0;
    size_t writePos = 0;
    size_t count = 0;
};

The following example shows how the write and read operations can be split into begin/end methods to avoid the overhead of copying the data items.


template<class T>
class DirectAccessCircularBuffer
{
public:
    explicit DirectAccessCircularBuffer(int size) : buffer(size) {}

    size_t size() const { return buffer.size(); }

    T& BeginWrite()
    {
        std::unique_lock lock(mutex);
        notFull.wait(lock, [this]() { return count < size(); });
        return buffer[writePos];
    }
    void EndWrite()
    {
        std::lock_guard lock(mutex);
        if (count == 0)
            notEmpty.notify_one();
        ++count;
        writePos = (writePos+1) % size();
    }
    const T& BeginRead()
    {
        std::unique_lock lock(mutex);
        notEmpty.wait(lock, [this]() { return count > 0; });
        return buffer[readPos];
    }
    void EndRead()
    {
        std::lock_guard lock(mutex);
        if (count == size())
            notFull.notify_one();
        --count;
        readPos = (readPos+1) % size();
    }

private:
    std::mutex mutex;
    std::condition_variable notEmpty;
    std::condition_variable notFull;
    std::vector<T> buffer;
    size_t readPos = 0;
    size_t writePos = 0;
    size_t count = 0;
};