Skip to main content
Code Review

Return to Question

Link formatting
Source Link
Zeta
  • 19.6k
  • 2
  • 57
  • 90

This implementation of a c++14 compatible Ring Buffer that was inspired by Pete Goodliffe ACCU article https://accu.org/index.php/journals/389. I also took some inspiration from Chris Riesbeck web page: https://www.cs.northwestern.edu/~riesbeck/programming/c++/stl-iterator-define.html.a Pete Goodliffe's ACCU article and the Chris Riesbeck web page .

This implementation of a c++14 compatible Ring Buffer that was inspired by Pete Goodliffe ACCU article https://accu.org/index.php/journals/389. I also took some inspiration from Chris Riesbeck web page: https://www.cs.northwestern.edu/~riesbeck/programming/c++/stl-iterator-define.html.

This implementation of a c++14 compatible Ring Buffer that was inspired by a Pete Goodliffe's ACCU article and the Chris Riesbeck web page .

Tweeted twitter.com/StackCodeReview/status/1066119443183542274
Source Link
davidbear
  • 587
  • 1
  • 5
  • 14

Ring Buffer Implementation in C++14

A ring buffer or circular buffer is a fixed sized queue that advances head and tail pointers in a modulo manner rather than moving the data. Ring buffers are often used in embedded computer design.

This implementation of a c++14 compatible Ring Buffer that was inspired by Pete Goodliffe ACCU article https://accu.org/index.php/journals/389. I also took some inspiration from Chris Riesbeck web page: https://www.cs.northwestern.edu/~riesbeck/programming/c++/stl-iterator-define.html.

As a hobbyist programmer, I started this project so I could learn some more about using templates. I intentionally avoided allocators since I don’t fully understand them (yet). I also did not attempt "emplace_back" for the same reason, but would love to learn about this. I used default copy/move constructors. Any suggestions or feedback that I can get about style, design and completeness of class will be appreciated. I believe that the iterator is basically STL compatible, but I would enjoy feedback on this aspect of the project as well.

#pragma once
#include <iostream>
#include <exception>
#include <cassert>
#include <vector>
#include <initializer_list>
template <class T>
class ring
{
 using value_type = T;
 using reference = T & ;
 using const_reference = const T &;
 using size_type = size_t;
 using circularBuffer = std::vector<value_type>;
 circularBuffer m_array;
 size_type m_head;
 size_type m_tail;
 size_type m_contents_size;
 const size_type m_array_size;
public:
 ring(size_type size = 8) : m_array(size),
 m_array_size(size),
 m_head(1),
 m_tail(0),
 m_contents_size(0) {
 assert(m_array_size > 1 && "size must be greater than 1");
 }
 ring(std::initializer_list<T> l) :m_array(l),
 m_array_size(l.size()),
 m_head(0),
 m_tail(l.size() - 1),
 m_contents_size(l.size()) {
 assert(m_array_size > 1 && "size must be greater than 1");
 }
 template <bool isconst> struct my_iterator;
 reference front() { return m_array[m_head]; }
 reference top() { return front(); }
 reference back() { return m_array[m_tail]; }
 const_reference front() const { return m_array[m_head]; }
 const_reference back() const { return m_array[m_tail]; }
 void clear();
 void push_back(const value_type &item);
 void push(const value_type &item) { push_back(item); }
 void pop_front() { increment_head(); }
 void pop() { pop_front(); }
 size_type size() const { return m_contents_size; }
 size_type capacity() const { return m_array_size; }
 bool empty() const;
 bool full() const;
 
 size_type max_size() const { return size_type(-1) / sizeof(value_type); }
 reference operator[](size_type index);
 const_reference operator[](size_type index) const;
 reference at(size_type index);
 const_reference at(size_type index) const;
 using iterator = my_iterator<false>;
 using const_iterator = my_iterator<true>;
 iterator begin();
 const_iterator begin() const;
 const_iterator cbegin() const;
 iterator rbegin();
 const_iterator rbegin() const;
 iterator end();
 const_iterator end() const;
 const_iterator cend() const;
 iterator rend();
 const_iterator rend() const;
private:
 void increment_tail();
 void increment_head();
 template <bool isconst = false>
 struct my_iterator
 {
 using iterator_category = std::random_access_iterator_tag;
 using difference_type = long long;
 using reference = typename std::conditional_t< isconst, T const &, T & >;
 using pointer = typename std::conditional_t< isconst, T const *, T * >;
 using vec_pointer = typename std::conditional_t<isconst, std::vector<T> const *, std::vector<T> *>;
 private:
 vec_pointer ptrToBuffer;
 size_type offset;
 size_type index;
 bool reverse;
 bool comparable(const my_iterator & other) {
 return (reverse == other.reverse);
 }
 public:
 my_iterator() : ptrToBuffer(nullptr), offset(0), index(0), reverse(false) {} //
 my_iterator(const ring<T>::my_iterator<false>& i) :
 ptrToBuffer(i.ptrToBuffer),
 offset(i.offset),
 index(i.index),
 reverse(i.reverse) {}
 reference operator*() { 
 if (reverse) 
 return (*ptrToBuffer)[(ptrToBuffer->size() + offset - index) % (ptrToBuffer->size())];
 return (*ptrToBuffer)[(offset+index)%(ptrToBuffer->size())]; 
 }
 reference operator[](size_type index) {
 my_iterator iter = *this;
 iter.index += index;
 return *iter;
 }
 pointer operator->() { return &(operator *()); }
 my_iterator& operator++ ()
 {
 ++index;
 return *this;
 };
 my_iterator operator ++(int)
 {
 my_iterator iter = *this;
 ++index;
 return iter;
 }
 my_iterator& operator --()
 {
 --index;
 return *this;
 }
 my_iterator operator --(int) {
 my_iterator iter = *this;
 --index;
 return iter;
 }
 friend my_iterator operator+(my_iterator lhs, int rhs) {
 lhs.index += rhs;
 return lhs;
 }
 friend my_iterator operator+(int lhs, my_iterator rhs) {
 rhs.index += lhs;
 return rhs;
 }
 my_iterator& operator+=(int n) {
 index += n;
 return *this;
 }
 friend my_iterator operator-(my_iterator lhs, int rhs) {
 lhs.index -= rhs;
 return lhs;
 }
 friend difference_type operator-(const my_iterator& lhs, const my_iterator& rhs) {
 lhs.index -= rhs;
 return lhs.index - rhs.index;
 }
 my_iterator& operator-=(int n) {
 index -= n;
 return *this;
 }
 bool operator==(const my_iterator &other)
 {
 if (comparable(other)) 
 return (index + offset == other.index + other.offset);
 return false;
 }
 bool operator!=(const my_iterator &other)
 {
 if (comparable(other)) return !this->operator==(other);
 return true;
 }
 bool operator<(const my_iterator &other)
 {
 if(comparable(other)) 
 return (index + offset < other.index + other.offset);
 return false;
 }
 bool operator<=(const my_iterator &other)
 {
 if(comparable(other)) 
 return (index + offset <= other.index + other.offset);
 return false;
 }
 bool operator >(const my_iterator &other)
 {
 if (comparable(other)) return !this->operator<=(other);
 return false;
 }
 bool operator>=(const my_iterator &other)
 {
 if (comparable(other)) return !this->operator<(other);
 return false;
 }
 friend class ring<T>;
 };
};
template<class T>
void ring<T>::push_back(const value_type & item)
{
 increment_tail();
 if (m_contents_size > m_array_size) increment_head(); // > full, == comma
 m_array[m_tail] = item;
}
template<class T>
void ring<T>::clear()
{
 m_head = 1;
 m_tail = m_contents_size = 0;
}
template<class T>
bool ring<T>::empty() const
{
 if (m_contents_size == 0) return true;
 return false;
}
template<class T>
inline bool ring<T>::full() const
{
 if (m_contents_size == m_array_size) return true; 
 return false;
}
template<class T>
typename ring<T>::const_reference ring<T>::operator[](size_type index) const
{
 index += m_head;
 index %= m_array_size;
 return m_array[index];
}
template<class T>
typename ring<T>::reference ring<T>::operator[](size_type index)
{
 const ring<T>& constMe = *this;
 return const_cast<reference>(constMe.operator[](index));
 // return const_cast<reference>(static_cast<const ring<T>&>(*this)[index]);
}
//*/
template<class T>
typename ring<T>::reference ring<T>::at(size_type index)
{
 if (index < m_contents_size) return this->operator[](index);
 throw std::out_of_range("index too large");
}
template<class T>
typename ring<T>::const_reference ring<T>::at(size_type index) const
{
 if (index < m_contents_size) return this->operator[](index);
 throw std::out_of_range("index too large");
}
template<class T>
typename ring<T>::iterator ring<T>::begin()
{
 iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_head;
 iter.index = 0;
 iter.reverse = false;
 return iter;
}
template<class T>
typename ring<T>::const_iterator ring<T>::begin() const
{
 const_iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_head;
 iter.index = 0;
 iter.reverse = false;
 return iter;
}
template<class T>
typename ring<T>::const_iterator ring<T>::cbegin() const
{
 const_iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_head;
 iter.index = 0;
 iter.reverse = false;
 return iter;
}
template<class T>
typename ring<T>::iterator ring<T>::rbegin()
{
 iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_tail;
 iter.index = 0;
 iter.reverse = true;
 return iter;
}
template<class T>
typename ring<T>::const_iterator ring<T>::rbegin() const
{
 const_iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_tail;
 iter.index = 0;
 iter.reverse = true;
 return iter;
}
template<class T>
typename ring<T>::iterator ring<T>::end()
{
 iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_head;
 iter.index = m_contents_size;
 iter.reverse = false;
 return iter;
}
template<class T>
typename ring<T>::const_iterator ring<T>::end() const
{
 const_iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_head;
 iter.index = m_contents_size;
 iter.reverse = false;
 return iter;
}
template<class T>
typename ring<T>::const_iterator ring<T>::cend() const
{
 const_iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_head;
 iter.index = m_contents_size;
 iter.reverse = false;
 return iter;
}
template<class T>
typename ring<T>::iterator ring<T>::rend()
{
 iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_tail;
 iter.index = m_contents_size;
 iter.reverse = true;
 return iter;
}
template<class T>
typename ring<T>::const_iterator ring<T>::rend() const
{
 const_iterator iter;
 iter.ptrToBuffer = &m_array;
 iter.offset = m_tail;
 iter.index = m_contents_size;
 iter.reverse = true;
 return iter;
}
template<class T>
void ring<T>::increment_tail()
{
 ++m_tail;
 ++m_contents_size;
 if (m_tail == m_array_size) m_tail = 0;
}
template<class T>
void ring<T>::increment_head()
{
 if (m_contents_size == 0) return;
 ++m_head;
 --m_contents_size;
 if (m_head == m_array_size) m_head = 0;
}

Here is the code that I used to test stuff out.

int main()
{
 ring<int> mybuf(10);
 for (size_t i = 0; i < 20; ++i) {
 mybuf.push(i);
 for (auto i = mybuf.begin(); i != mybuf.end(); ++i) cout << *i << ": ";
 if (mybuf.full()) cout << "full";
 cout << '\n';
 }
 cout << "Buffer Size: " << mybuf.size() << '\n';
 for (size_t i = 0; i < mybuf.size() + 1; ++i) {
 try
 {
 cout << mybuf.at(i) << ": ";
 }
 catch (std::exception e)
 {
 cout << e.what() << '\n';
 continue;
 }
 }
 cout << '\n';
 auto start = mybuf.begin();
 start += 1;
 cout << "start++: " << *start << '\n';
 ring<int>::const_iterator cstart(start);
 cout << "cstart(start)++: " << *(++cstart) << '\n';
 cout << "--start: " << *(--start) << '\n';
 if (start == mybuf.begin()) cout << "Start is mybuf.begin\n";
 else cout << "Lost!\n";
 cout << "Push!\n";
 mybuf.push(100);
 if (start == mybuf.begin()) cout << "In the begining :-)\n";
 else cout << "Start is no longer mybuf.begin\n";
 start = mybuf.begin();
 cout << "after push, start: " << *start << '\n';
 cout << "forwards: ";
 for (auto i = mybuf.begin(); i < mybuf.end(); i+=2) cout << *i << ": ";
 cout << '\n';
 cout << "backwards: ";
 for (auto i = mybuf.rbegin(); i < mybuf.rend(); i+=2) cout << *i << ": ";
 cout << '\n';
 cout << "mybuf[0]: "<<mybuf[0] << " " << "\nPush!\n\n";
 mybuf.push(20);
 for (size_t i = 0; i < mybuf.size(); ++i) cout << mybuf[i] << ": ";
 cout << '\n';
 cout << "pop: " << mybuf.top() << '\n';
 mybuf.pop();
 cout << "new front: " << mybuf[0] << " new size: ";
 cout << mybuf.size() << '\n';
 cstart = mybuf.end();
 cout << "last: " << *(--cstart) << '\n';
 for (auto i = mybuf.begin(); i != mybuf.end(); ++i) cout << *i << ": ";
 cout << '\n';
 cout << "pop again: " << mybuf.front() << '\n';
 mybuf.pop();
 cstart = mybuf.rbegin();
 cout << "last: " << *cstart << '\n';
 for (auto i = mybuf.begin(); i != mybuf.end(); ++i) cout << *i << ": ";
 cout << "\n\nclone: ";
 ring<int> cbuf(mybuf);
 for (auto i = std::find(mybuf.begin(),mybuf.end(),100); i != cbuf.end(); ++i) cout << *i << ": ";
 auto iter = cbuf.cbegin();
 cout << "\nbegin[3] = " << iter[3];
 cout << '\n' << '\n';
 cout << "Hello World!\n";
}

And this is the output from that test.

0:
0: 1:
0: 1: 2:
0: 1: 2: 3:
0: 1: 2: 3: 4:
0: 1: 2: 3: 4: 5:
0: 1: 2: 3: 4: 5: 6:
0: 1: 2: 3: 4: 5: 6: 7:
0: 1: 2: 3: 4: 5: 6: 7: 8:
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: full
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: full
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: full
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: full
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: full
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: full
6: 7: 8: 9: 10: 11: 12: 13: 14: 15: full
7: 8: 9: 10: 11: 12: 13: 14: 15: 16: full
8: 9: 10: 11: 12: 13: 14: 15: 16: 17: full
9: 10: 11: 12: 13: 14: 15: 16: 17: 18: full
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: full
Buffer Size: 10
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: index too large
start++: 11
cstart(start)++: 12
--start: 10
Start is mybuf.begin
Push!
Start is no longer mybuf.begin
after push, start: 11
forwards: 11: 13: 15: 17: 19:
backwards: 100: 18: 16: 14: 12:
mybuf[0]: 11
Push!
12: 13: 14: 15: 16: 17: 18: 19: 100: 20:
pop: 12
new front: 13 new size: 9
last: 20
13: 14: 15: 16: 17: 18: 19: 100: 20:
pop again: 13
last: 20
14: 15: 16: 17: 18: 19: 100: 20:
clone: 100: 20:
begin[3] = 17
Hello World!
lang-cpp

AltStyle によって変換されたページ (->オリジナル) /