7

A C++ wapper around a FreeRTOS queue can be simplified into something like this:

template<typename T>
class Queue<T>
{
    public:
    bool push(const T& item)
    {
        return xQueueSendToBack(handle, &item, 0) == pdTRUE;
    }

    bool pop(T& target)
    {
        return xQueueReceive(handle, &target, 0) == pdTRUE;
    }
    private:
    QueueHandle_t handle;
}

The documentation of xQueueSendToBack states:

The item is queued by copy, not by reference.

Unfortunately, it is literally by copy, because it all ends in a memcpy, which makes sense since it is a C API. While this works well for plain old data, more complex items such as the following event message give serious problems.

class ConnectionStatusEvent
{
    public:
        ConnectionStatusEvent() = default;
        ConnectionStatusEvent(std::shared_ptr<ISocket> sock)
            : sock(sock)
              {
              }

              const std::shared_ptr<ISocket>& get_socket() const
              {
                  return sock;
              }

    private:
        const std::shared_ptr<ISocket> sock;
        bool connected;
};

The problem is obviously the std::shared_ptr which doesn't work at all with a memcpy since its copy constructor/assignment operator isn't called when copied onto the queue, resulting in premature deletion of the held object when the event message, and thus the shared_ptr, goes out of scope.

I could solve this by using dynamically allocated T-instances and change the queues to only contain pointers to the instance, but I'd rather not do that since this shall run on an embedded system and I very much want to keep the memory static at run-time.

My current plan is to change the queue to contain pointers to a locally held memory area in the wrapper class in which I can implement full C++ object-copy, but as I'd also need to protect that memory area against multiple thread access, it essentially defeats the already thread-safe implementation of the FreeRTOS queues (which surely are more efficient than any implementation I can write myself) I might as well skip them entirely.

Finally, the question:

Before I implement my own queue, are there any tricks I can use to make the FreeRTOS queues function with C++ object instances, in particular std::shared_ptr?

Per
  • 1,074
  • 6
  • 19
  • The standard way to describe this issue is that your C-style queue there is only compatible with C++ types for which std::is_trivial::value is true. C++ may dutifully increment the shared_ptr reference count when it copies it off to your queue but your queue is never going to decrement it, causing a leak. This is because C-style code doesn't call constructors, destructors, or assignment operators. TL;DR: there is no point in sending this thing a shared_ptr. – hoodaticus Aug 04 '17 at 18:15
  • Yes, that sums it up in much fewer words :) – Per Aug 04 '17 at 18:22
  • Yes but I'm afraid that's all that can be done. You've already identified the only real solution to this which is to send it raw pointers and then manage the memory yourself elsewhere :/ – hoodaticus Aug 04 '17 at 18:23
  • Yeah, I believe that to be the case too. On the other hand, I've been surprised by the ingenuity of some people on SO before so someone might have some cool trick. – Per Aug 04 '17 at 18:34

1 Answers1

0

The issue is what happens to the original once you put the pointer into the queue. Copying seems trivial but not optimal.

To get around this issue i use a mailbox instead of a queue:

T* data = (T*) osMailAlloc(m_mail, osWaitForever);
...
osMailPut (m_mail, data);

Where you allocate the pointer explicitly to begin with. And just add the pointer to the mailbox.

And to retrieve:

osEvent ev = osMailGet(m_mail, osWaitForever);
...
osStatus freeStatus = osMailFree(m_mail, p);

All can be neatly warpend into c++ template methods.

David
  • 1
  • 4