We use this C++ circular queue class in our C++ Ring Buffer Class (coming soon) that performs buffering of audio/video data streams. The underlying data transport layer is usually UDP over sockets. These programs are usually multi-threaded, using thread synchronization techniques (such as <semaphore>) to manage write/read of the data across threads.
We restrict the queue size to that of a power of two, taking advantage of the performance boost that bitwise operations have over multiplication operations. This my not be the most optimal approach for some hardware. Testing and optimization are encouraged. Also, the queue elements are allocated during initialization, so no allocations are performed during the buffering process.
License
// NOTE: this source code is provided as implicit-
// inline for brevity. It is suggested that
// programmers break this source code up into
// header and implementation files.
#include <cstdint>
class CircularQueue
{
public:
// total number of enqueue operations
uint32_t enqueues;
// total number of dequeue operations
uint32_t dequeues;
// Index Mask - a bitmask
uint32_t indexMask;
// pointer to array of elements
void** elementsArray;
CircularQueue()
{
this->elementsArray = nullptr;
};
~CircularQueue()
{
reset();
};
void reset()
{
// free array of elements:
if (this->elementsArray)
delete [] this->elementsArray;
this->enqueues = 0;
this->dequeues = 0;
this->indexMask = 0;
this->elementsArray = nullptr;
}
bool init(uint32_t size)
{
// allow object re-use:
reset();
// verify size is not zero:
if (size == 0)
return false;
// calculate the bit mask:
this->indexMask = size - 1;
// verify size is a power of 2:
if (size & (this->indexMask))
return false;
// allocate the queue elements:
this->elementsArray = new void*[size];
// will return true only if all of the above succeeded:
return (this->elementsArray != nullptr);
};
void* enqueue(void* objectPointer)
{
// check for overflow:
if (size() > this->indexMask)
return nullptr;
// calculate the enqueue index:
uint32_t enqueueIndex =
(this->enqueues & this->indexMask);
// set the element value:
*(this->elementsArray + enqueueIndex) =
objectPointer;
// increment enqueue count:
this->enqueues++;
return objectPointer;
};
void* dequeue()
{
// get the element value:
void* objectPointer = peek();
// increment dequeue count (if no peek error):
if (objectPointer != nullptr)
this->dequeues++;
return objectPointer;
};
void* peek()
{
// check for underflow:
if (size() == 0)
return nullptr;
// calculate the dequeue index:
uint32_t dequeueIndex =
(this->dequeues & this->indexMask);
// return the element value:
return *(this->elementsArray + dequeueIndex);
};
// returns the distance between dequeues and enqueues
// (the size of the actual queue contents):
uint32_t size()
{
if (this->dequeues < this->enqueues)
return (this->enqueues - this->dequeues);
else
return (this->dequeues - this->enqueues);
};
};
The above size() member function would look shorter if it was constructed like this:
uint32_t size()
{
return abs(this->enqueues - this->dequeues);
};
But the abs() function is typically implemented like this:
int abs(int n)
{
if (n >= 0)
return n;
else
return -n;
};
Since the operands are unsigned (never less than 0), the size() member function forgoes calling the abs() function and tests one condition of its own in place of the “n >= 0″ test in the abs() function. There is also an efficiency increase by removing the overhead of another function call.

Leave a Reply