Just wrote this small ring buffer system for an embedded device using C++98. Looking for cc, advice and bugs.
Please check it out!
https://github.com/Bambofy/EmbeddedRingBuffer
/* Changelog ------------------------------------------------------------------
* 06/06/2021 Initial version
* ---------------------------------------------------------------------------*/
#ifndef RINGBUFFER_RINGBUFFER_H
#define RINGBUFFER_RINGBUFFER_H
#include "Block.h"
/**
* @brief A ring buffer is a FIFO structure that can be used to
* spool data between devices.
*
* There is a Skip() function that allows the client to
* control when the read cursor is changed. This is so the
* client can perform an action after Read() without the
* write cursor overwriting data while the read block is used.
*
* For e.g with the sequence of events:
* 1. Read(1000, false)
* 2. Busy writing to sd card for 5 seconds
* 3. Skip()
*
* Because the skip isn't called until the writing
* has finished, another thread can .Append() without
* corrupting the data being written.
*
*
* @attention The ring buffer can only contain Length-1 number of entries,
* because the last index is reserved for overrun checks.
*
* @tparam Length The length of the backing store array.
* @tparam T The type of data stored.
*/
template<unsigned int LENGTH, class T>
class RingBuffer
{
public:
RingBuffer() : read_position(0), write_position(0)
{
}
~RingBuffer()
{
memset(data, 0, LENGTH);
}
/**
* @brief Appends a value the end of the
* buffer.
*/
void Append(T value)
{
/*
* If the next position is where the read cursor
* is then we have a full buffer.
*/
bool buffer_full;
buffer_full = ((write_position + 1U) % LENGTH) == read_position;
if (buffer_full)
{
/*
* Tried to append a value while the buffer is full.
*/
overrun_flag = true;
}
else
{
/*
* Buffer isn't full yet, write to the curr write position
* and increment it by 1.
*/
overrun_flag = false;
data[write_position] = value;
write_position = (write_position + 1U) % LENGTH;
}
}
/**
* @brief Retrieve a continuous block of
* valid buffered data.
* @param num_reads_requested How many reads are required.
* @param skip Whether to increment the read position
* automatically, (false for manual skip
* control)
* @return A block of items containing the maximum
* number the buffer can provide at this time.
*/
Block<T> Read(unsigned int num_reads_requested, bool skip = true)
{
bool bridges_zero;
Block<T> block;
/*
* Make sure the number of reads does not bridge the 0 index.
* This is because we can only provide 1 contiguous block at
* a time.
*/
bridges_zero = (read_position > write_position);
if (bridges_zero)
{
unsigned int reads_to_end;
bool req_surpasses_buffer_end;
reads_to_end = LENGTH - read_position;
req_surpasses_buffer_end = num_reads_requested > reads_to_end;
if (req_surpasses_buffer_end)
{
/*
* If the block requested exceeds the buffer end. Then
* return a block that reaches the end and no more.
*/
block.SetStart(&(data[read_position]));
block.SetLength(reads_to_end);
if (skip)
{
read_position = (read_position + reads_to_end) % LENGTH;
}
}
else
{
/*
* If the block requested does not exceed 0
* then return a block that reaches the number of reads required.
*/
block.SetStart(&(data[read_position]));
block.SetLength(num_reads_requested);
if (skip)
{
read_position =
(read_position + num_reads_requested) % LENGTH;
}
}
}
else
{
/*
* If the block doesn't bridge the zero then
* return the maximum number of reads to the write
* cursor.
*/
unsigned int max_num_reads;
unsigned int num_reads_to_write_position;
num_reads_to_write_position = (write_position - read_position);
if (num_reads_requested > num_reads_to_write_position)
{
/*
* If the block length requested exceeds the
* number of items available, then restrict
* the block length to the distance to the write position.
*/
max_num_reads = num_reads_to_write_position;
}
else
{
/*
* If the block length requested does not exceed the
* number of items available then the entire
* block is valid.
*/
max_num_reads = num_reads_requested;
}
block.SetStart(&(data[read_position]));
block.SetLength(max_num_reads);
if (skip)
{
read_position = (read_position + max_num_reads) % LENGTH;
}
}
return block;
}
/**
* @brief Advances the read position.
*
*/
void Skip(unsigned int num_reads)
{
read_position = (read_position + num_reads) % LENGTH;
}
bool Overrun()
{
return overrun_flag;
}
unsigned int Length()
{
return LENGTH;
}
private:
unsigned int read_position;
unsigned int write_position;
T data[LENGTH];
bool overrun_flag;
};
#endif
/* Changelog ------------------------------------------------------------------
* 06/06/2021 Initial version
* ---------------------------------------------------------------------------*/
#ifndef RINGBUFFER_BLOCK_H
#define RINGBUFFER_BLOCK_H
#include <cstddef>
/**
* @brief A block represents a continuous section
* of the ring buffer.
* @tparam T The type of data stored in the ring buffer.
*/
template<class T>
class Block
{
public:
Block() : start(nullptr), length(0)
{
}
~Block()
{
}
/**
* @brief Sets the block's starting
* position to a point in memory.
*/
void SetStart(T* start)
{
this->start = start;
}
/**
* @brief Sets the number of items in the
* block.
*/
void SetLength(unsigned int length)
{
this->length = length;
}
/**
* @return The block's starting
* point in memory.
*/
T* Start()
{
return this->start;
}
/**
* @return The number of items in the block.
*/
unsigned int Length()
{
return this->length;
}
/**
* @param index The index of the item in the block.
* @return The item in the block at the index.
*/
T At(unsigned int index)
{
return this->start[index];
}
private:
T* start;
unsigned int length;
};
#endif
Here is a test:
#include <iostream>
#include "RingBuffer.h"
int main()
{
RingBuffer<100, int> buffer;
Block<int> block;
/* Write 100 ints */
for (int i = 0; i < buffer.Length(); i++)
{
buffer.Append(i);
}
/* Read a block */
block = buffer.Read(100);
/* Print out the block */
for (int i = 0; i < block.Length(); i++)
{
std::cout << block.At(i) << std::endl;
}
/* Read another block */
block = buffer.Read(1000);
/* Print out the block */
for (int i = 0; i < block.Length(); i++)
{
std::cout << block.At(i) << std::endl;
}
return 0;
}
ifndeftalks about aRINGBUFFER_RINGBUFFER_H(double ringbuffer) or is this a typo? \$\endgroup\$nullptr. I think you may not be using the version of C++ you think you’re using. (Nobody is still using C++98 anymore these days.) \$\endgroup\$std::atomic). \$\endgroup\$