With the following implementation, there's no bounds checking on reads, and writing involves N pointer decrements, two additions, and a single bound check. I tested it against Boost's circular buffer implementation in a typical usage case. The test involved pushing a double onto the buffer, and then reading each value into a temp variable, similar to the process of adding to a delay line and then reading the past samples for filtering. I tested 1000, 10000, and 100000 iterations with buffer lengths of 128, 512, 1024, and 3072. My implementation was 3-4 times faster in all cases.
It's templated of course, and works with dynamic objects as well. There's a flag in the constructor that sets whether it should call delete on its contents when the buffer is destructed. If you're using dynamically allocated objects, you should call Dyn_Push() instead of Push() to prevent memory leaks. Apart from that, it's pretty simple. The zero indexed element is always the most recent. I didn't add a setter method since that's not really useful in the context of a delay line buffer, but it would be trivial to add.
Feel free to use and modify this to your heart's content. Hopefully it's useful in speeding up your algorithms. Any corrections are welcome. Cheers.
Here's the code:
Code: Select all
#pragma once
template <class T>
class CircularBuffer
{
public:
CircularBuffer(int size, bool containsDynamicObjects = false);
~CircularBuffer();
void Push(T element);
void Dyn_Push(T element);
inline const T& operator[](int index);
inline int Size() { return mSize; };
private:
T* mBuffer;
T** mPointerBuffer;
int mWrapIndex;
int mSize;
bool mIsDynamic;
};
// Template Implementation
template <class T>
CircularBuffer<T>::CircularBuffer(int size, bool containsDynamicObjects)
:mBuffer(new T[size]),
mPointerBuffer(new T*[size]),
mWrapIndex(0),
mSize(size),
mIsDynamic(containsDynamicObjects)
{
for (int i = 0; i < size; ++i)
{
mBuffer[i] = static_cast<T>(0.0);
mPointerBuffer[i] = &mBuffer[i];
}
}
template <class T>
CircularBuffer<T>::~CircularBuffer()
{
if (mIsDynamic)
{
for (int i = 0; i < mSize; ++i) delete mBuffer[i];
}
delete[] mBuffer;
delete[] mPointerBuffer;
}
template <class T>
const T& CircularBuffer<T>::operator[](int index)
{
if ((index < 0) || (index >= mSize)) return T();
return *mPointerBuffer[index];
}
// Leaks memory if used with dynamically allocated objects. Use Dyn_Push instead.
template <class T>
void CircularBuffer<T>::Push(T element)
{
for (int i = 0; i < mSize; ++i)
{
--mPointerBuffer[i];
}
mPointerBuffer[mWrapIndex] += mSize;
++mWrapIndex;
if (mWrapIndex >= mSize) mWrapIndex -= mSize;
*mPointerBuffer[0] = element;
}
// For use with dynamically allocated objects. Calls delete on objects pushed off the buffer.
template <class T>
void CircularBuffer<T>::Dyn_Push(T element)
{
for (int i = 0; i < mSize; ++i)
{
--mPointerBuffer[i];
}
mPointerBuffer[mWrapIndex] += mSize;
++mWrapIndex;
if (mWrapIndex >= mSize) mWrapIndex -= mSize;
delete mPointerBuffer[0];
*mPointerBuffer[0] = element;
}