I am trying to implement a circular buffer that utilizes mutex in order to be thread safe. I have been using the following code:
#include <cstdio>
#include <memory>
#include <mutex>
template <class T>
class circular_buffer {
public:
explicit circular_buffer(size_t size) :
buf_(std::unique_ptr<T[]>(new T[size])),
max_size_(size)
{
}
void put(T item)
{
std::lock_guard<std::mutex> lock(mutex_);
buf_[head_] = item;
if (full_)
{
tail_ = (tail_ + 1) % max_size_;
}
head_ = (head_ + 1) % max_size_;
full_ = head_ == tail_;
}
T get()
{
std::lock_guard<std::mutex> lock(mutex_);
if (empty())
{
return T();
}
//Read data and advance the tail (we now have a free space)
auto val = buf_[tail_];
full_ = false;
tail_ = (tail_ + 1) % max_size_;
return val;
}
void reset()
{
std::lock_guard<std::mutex> lock(mutex_);
head_ = tail_;
full_ = false;
}
bool empty() const
{
//if head and tail are equal, we are empty
return (!full_ && (head_ == tail_));
}
bool full() const
{
//If tail is ahead the head by 1, we are full
return full_;
}
size_t capacity() const
{
return max_size_;
}
size_t size() const
{
size_t size = max_size_;
if (!full_)
{
if (head_ >= tail_)
{
size = head_ - tail_;
}
else
{
size = max_size_ + head_ - tail_;
}
}
return size;
}
private:
std::mutex mutex_;
std::unique_ptr<T[]> buf_;
size_t head_ = 0;
size_t tail_ = 0;
const size_t max_size_;
bool full_ = 0;
};
The problem with this code is that I cannot seem to get it to work with arrays of floats. I am getting a function returns array error (from the get function). I am not entirely sure how to fix this (tried passing in an array and using get() function to point that array, but this didn't work either). Sorry if this question is a bit abstract, I am honestly completely in over my head on this one (first job as dev and literally my 6 days into job they're having me make a very complex radar mapping app). Let me know if you need any clarifications on anything.
edit: Thanks everyone! Michael's answer worked, and thanks for the suggestions. I honestly feel like I'm drowning over my head right now so all the tips are extremely helpful!
First of all, be aware that if anyone uses the size()
, empty()
, or full()
methods of an instance of this class template while someone else is concurrently using get()
, put()
, or reset()
, you will end up with undefined behavior. size()
or empty()
will also have to lock the mutex because they read the values of objects (full_
, head_
, and tail_
) that may potentially be modified concurrently. Apart from that, it would seem to me that put()
always writes something, even if the queue is full. That's probably not what one would typically want.
Based on your description, I assume the problem you're asking about has to do with trying to create, e.g., a circular_buffer<float[4]>
. Think about what the get()
method would turn into if you substitute the type float[4]
for T
:
float get()[4] { … }
You end up with a function returning an array. Functions are not allowed to return arrays [dcl.fct]/11.* That's why you end up with a compiler error as soon as you'd call the get()
method on such a circular_buffer
. Use, e.g., std::array
instead: circular_buffer<std::array<float, 4>>
.
*) I believe this is most likely for historical reasons. The way array types were designed to behave when passed to functions in C was such that arrays would effectively end up being passed by reference; there is no good way for a function to return an array by reference, and returning by value would be inconsistent with how they are passed in. Thus, it's probably best to just disallow arrays to be returned at all…