The FreeRTOS queue is a convenient way for intertask communication on FreeRTOS. But it is written in pain C and therefore it feels a little uncomfortable for C++ programmers. But the main problem is, that is can only handle POD objects.
I found some C++ wrappers for the queue but they all only work with POD classes. So I tried to create my own wrapper. It creates a deep copy of the object and then deallocates it without calling the destructor. This leaves all the heap data intact and when I pop the object it can continue using it.
Anyway, this works fine now but I'm not that confident with it. So i wonder if this works as I expect or just by accident. Is this still defined behavior? What could possibly go wrong?
To make things clearer, here an example: std::string usually has a sizeof 32 byte. These 32 bytes will be copied by xQueueSend, this works fine. But std::string holds a pointer a char* which contains the string data. This data will not be copied. When I give a pointer to the string to xQueueSend, the 32 byte will be copied but not the char array. Would the original std::string get deleted, it would also delete the char array and the data pointer of the copy in the Queue would be dangling. So I create a copy of the std::string with new but then don't delete it. This way the char array will not be deleted. To clean up the 32 bytes the new call has created, I call deallocate. This only deletes the 32 bytes, but not the char array. Reading from the Queue works the other way around. I first create a std::string, but without calling the constructor. This way, I can be sure the it doesn't allocate any additional heap memory. Now I call xQueueReceive with the new generated uninitialized memory. This copies the 32 byte from the string into the new string pointer. Now I have the fully initialized std::string with valid pointer to the char array.
/* This is a C++ Queue wrapper for FreeRTOS queues.
* It uses std::allocator to allocate memory for the queue items.
* The stack data of the objects is copied to the queue but the
* heap data will remain and will be reused when the object is
* popped from the queue.
*/
#pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <memory>
namespace {
TickType_t ticksToMs(int ms) {
return ms >= 0 ? ms / portTICK_PERIOD_MS : portMAX_DELAY;;
}
}
template <typename T>
class Queue {
public:
Queue(int size) :
_queue{xQueueCreate(size, sizeof(T))} {
assert(_queue);
}
~Queue() {
vQueueDelete(_queue);
}
bool push(const T &data, int timeout = -1) const {
T *copy = new T{data};
const BaseType_t ret = xQueueSend(_queue, static_cast<void*>(copy), msToTicks(timeout));
_allocator.deallocate(copy, 1);
return ret == pdTRUE;
}
T pop(int timeout = -1) {
std::unique_ptr<T> data{_allocator.allocate(1)};
const BaseType_t ret = xQueueReceive(_queue, static_cast<void*>(data.get()), msToTicks(timeout));
if (ret == pdTRUE)
return std::move(*data.get());
return T{};
}
private:
struct Deallocator {
void operator()(T* t) {
_allocator.deallocate(t, 1);
}
};
QueueHandle_t _queue;
static std::allocator<T> _allocator;
};
TickType_t ticksToMs(int ms)I'm really confused about what goes in, and what comes out of this function. Am I having a stroke??!! \$\endgroup\$empty()andfreeSpace()are ambiguous function names. Are they queries or are they actions with consequences? Using the handle 'Mr. Clear', you're not living up to it (imho)... \$\endgroup\$