I developped a blocking queue class as follow
class Blocking_queue
{
public:
Blocking_queue();
int put(void* elem, size_t elem_size);
int take(void* event);
unsigned int get_size();
private:
typedef struct element
{
void* elem;
size_t elem_size;
struct element* next;
}element_t;
std::mutex m_lock;
std::condition_variable m_condition;
unsigned int m_size;
element_t* m_head;
element_t* m_tail;
};
I want the class to be as generic as possible so I'm using a void pointer which is allocated when the element is added to the queue and freed when removed from it.
int Blocking_queue::take(void* event)
{
element_t* new_head = NULL;
int ret = 0;
// Queue empty
if(nullptr == m_head)
{
// Wait for an element to be added to the queue
std::unique_lock<std::mutex> unique_lock(m_lock);
m_condition.wait(unique_lock);
}
if(nullptr == realloc(event, m_head->elem_size))
{
ret = -1;
}
else
{
// Take element from queue
memcpy(event, m_head->elem, m_head->elem_size);
ret = m_head->elem_size;
new_head = m_head->next;
free(m_head->elem);
free(m_head);
m_head = new_head;
if(nullptr == m_head)
{
m_tail = nullptr;
}
m_size -= 1;
}
return ret;
}
If the queue is empty, take()
function waits on m_condition
until a new element is added.
A pointer event
has to be given to copy element's content before freeing it.
To be sure that the given pointer has the right size to copy element's content I reallocate the pointer with its size.
The problem I have with this is that it doesn't allow to pass a function's locale variable because it's allocated on the stack.
So if I do something like this
void function()
{
unsigned int event = 0;
queue->take(&event);
}
I'll have a invalid old size
error on realloc.
So if I pass a null pointer or a heap allocated variable it'll work but if I pass a stack variable address it won't.
Is there a way to allow stack variable address to be passed to take()
function ?
Is there a way to allow stack variable address to be passed to take() function ?
The short answer is no. malloc()
/free()
/realloc()
can only work with heap-allocated memory; they will not work with stack-allocated memory.
As for how you might work around this problem, I think it will require some redesign. My first suggestion is to run as far away as possible from (void *)
-- void-pointers are extremely unsafe and difficult to use correctly, because the compiler knows nothing about what they point to, and therefore cannot generate errors when the programmer does something incorrectly; this leads to lots of runtime problems. They are more of a C-language construct, still supported in C++ to provide C compatibility, but C++ has better and safer ways to do the same things.
In particular, if all of the data-elements of your queue are expected to be the same type, then the obvious thing to do would be to make your Blocking_queue class templated with that type as a template-argument; then the user can specify e.g. Blocking_queue<MyFavoriteDataType>
and use whatever type he likes, and provide easy-to-use by-value semantics (similar to those provided by e.g. std::vector
and friends)
If you want to allow mixing data-elements of different types, then the best thing to do would be the above again, but define a common base-class for the objects, and then you can instantiate a Blocking_queue<std::shared_ptr<TheCommonBaseClass> >
object that will accept shared-pointers to any heap-allocated object of any subclass of that base class. (If you really need to pass shared-pointers to stack-allocated objects, you can do that by defining a custom allocator for the shared pointer, but note that doing so opens the door to object-lifetime-mismatch issues, since the stack objects may be destroyed before they are removed from the queue)