markt1964
markt1964

Reputation: 2836

Creating a template container for both copyable and non-copyable types

I am trying to create a templated single producer/single consumer queue-like container that will support any C++ type (other than references).

I intend to have have two public methods, enqueue and dequeue, to support operations on the container, and my initial implementation of the container was as follows:

template<typename T, size_t N> class ring_queue {
public:
    bool enqueue(const T &v)
    {
        auto pos = end++;
        if (start % N == end % N) {
            --end;
            return false;
        }
        end %= N;
        std::optional<T> value(v);
        std::swap(storage[pos], value);
        return true;
    }

    bool dequeue(T &v)
    {
        size_t pos = start++;
        if (pos == end % N) {
            --start;
            return false;
        }
        std::optional<T> value;
        std::swap(storage[pos], value);
        v = std::move(value.value());
        start %= N;
        return true;
    }
private:
    std::array<std::optional<T>,N> storage;
    size_t start = 0;
    size_t end = 0;
};

As you can see, I'm using an std::optional here for the storage, the idea being that the only elements in the storage that contain values are those that are in the queue, so that if the queue is destroyed when it is empty (start == end), there will be no T destructors being called.

When T is a simple copy-able type such as int, or even std::string this works fine.

When T is an std::unique_ptr, for example, then things fall apart. The idea with non-copyable types is that I want the container to own them while they are contained and then released when the element is dequeued.

How do I do this?

Upvotes: 1

Views: 96

Answers (1)

Ted Lyngmo
Ted Lyngmo

Reputation: 117513

You could make enqueue take a forwarding reference instead to forward whatever arguments the function gets to the constructor of T. You could skip the temporary std::optional<T> + swap too. Example:

#include <utility>

// ...

    template<class... Args>
    bool enqueue(Args&&... v) {
        // ...
        storage[pos] = T{std::forward<Args>(v)...};
        // ...
    }

This would then work fine with non-copyables:

    ring_queue<std::unique_ptr<int>, 3> rq;
    rq.enqueue(std::make_unique<int>(1));

Upvotes: 1

Related Questions