-1

I built a C++ wrapper around the FreeRTOS timer API. My class statically allocates the timer control block which is operated by a FreeRTOS thread in the background. This means that if I move or copy this object, the control block will be moved/copied as well BUT the thread wont notice that. Because of that I consider the object non-copyable and non-movable.

Here's the outline:

#include <cstdint>
#include <concepts>

template <std::invocable Cb>
class timer
{
public:
    timer() = default;
    timer(Cb cb, TickType_t timer_period, bool auto_reload = false)
        : cb_{ cb }
    {
        xTimerCreateStatic("timer", timer_period, auto_reload, static_cast<void*>(this), &timer::timer_expired_cb, &buf_);
    }

    timer(const timer&) = delete;
    timer(timer&&) = delete;
    auto operator=(const timer&) = delete;
    auto operator=(timer&&) = delete;
    // ...
private:
    Cb cb_;
    TimerHandle_t handle_;
    StaticTimer_t buf_;
};

Now I want to push multiple of this timer objects into a C++ container which I can dynamically extend or shrink as objects enter or leave the container. Is there a stdlib container that doesn't require objects to be moveable or copyable and still provides all the functionality?

glades
  • 3,778
  • 1
  • 12
  • 34
  • pushing something into a container already requires to move or copy. You need to restrict to emplace – 463035818_is_not_an_ai Dec 05 '22 at 09:43
  • anyhow, this answer can be answered by simply going through the list of containers and check their requirements. https://en.cppreference.com/w/cpp/container – 463035818_is_not_an_ai Dec 05 '22 at 09:45
  • @StoryTeller: How does this have *anything* to do with iterator invalidation rules? – bitmask Dec 05 '22 at 10:00
  • `std::vector>` or `std::list`. Depends on what you want to do with it. Though "FreeRTOS" suggests a highly constrained environment, where standard library containers (and FreeRTOS wrappers) are usually considered 'too heavy' and not used. – rustyx Dec 05 '22 at 10:02
  • @bitmask - You mean, how does the validly of something that is essentially a pointer proxy related with stability in a container? How is a post enumerating it for **all** containers related to the OP's general question? Wanna think about it!? Or is spoon feeding mandatory? – StoryTeller - Unslander Monica Dec 05 '22 at 10:03
  • @rustyx std::list would be very light. Also I'm using the pmr namespace to avoid full out dynamic allocation but still use the benefits of the container. I think I'll probably go with std::pmr::list – glades Dec 05 '22 at 10:03
  • I'm not familiar with FreeRTOS, but if you can update address of control block in OS when it is moved, then you can make your object moveable. Just update this address each time it is moved. – sklott Dec 05 '22 at 10:12
  • @StoryTeller-UnslanderMonica I don't know why you closed the question it's not about iterator invalidation, iterator invalidation and moveablilit/copyability are different concepts. I can think of containers that invalidate iterators while keeping individual buckets at the same memory address. – glades Dec 05 '22 at 10:17
  • @sklott I know but the address is presumably operated from a task which I don't have control over and I can't just signal it to update my objects address. – glades Dec 05 '22 at 10:18
  • std::deque can work with no moveable/copyable types if you use emplace. – jls28 Dec 05 '22 at 10:24
  • @StoryTeller-UnslanderMonica My bad tbh I didn't really get what they meant by references but it makes sense now. – glades Dec 05 '22 at 12:00

1 Answers1

1

I see four basic options:

  • std::list<timer>: This might be one of the very rare cases when using std::list is the best option. Insertion into the list must be done via one of the emplace member functions, as you cannot move in an already existing object.
  • std::vector<std::unique_ptr<timer>>: In case construction of timer objects is not directly associated with keeping them in your container. This has the disadvantage that accessing and removing entries slightly more work than std::list. But your timer factory doesn't need to know how you plan to store the object.
  • std::set<timer>: You stated that you have new objects arriving and departing. If you have a lot of them, maybe you want to avoid finding them in a linear container. std::set also has no reallocations and offers you a nice interface for finding and erasing objects. But clearly it has more overhead than the other two suggestions.
  • std::array<std::optional<timer>, N>: If you know the maximal number N of timers you will see at runtime, consider a fixed-size array with optionals in it. This has no pointer-indirections but you likely have a number of branches when searching an item.
bitmask
  • 32,434
  • 14
  • 99
  • 159