Queue implementation using unique_ptr I am trying out different data structures to learn more about smart pointers. I have created a queue implementation which has push, pop, front, back and size functions.

#include &lt;iostream&gt;
#include &lt;memory&gt;
#include &lt;cstddef&gt;

template&lt;typename T&gt;
class Queue
{
    std::unique_ptr&lt;T[]&gt; q_ptr;
    int front_idx = -1;
    int back_idx = -1;
    int capacity = 0;
public:
    Queue(std::size_t space)
    {
        q_ptr = std::unique_ptr&lt;T[]&gt;(new T[space]);
        capacity = space;
    }
    void push(T value);
    void pop();
    T front() const;
    T back() const;
    std::size_t size() const;
};

template&lt;typename T&gt;
void Queue&lt;T&gt;::push(T value)
{
    if(front_idx == -1)
    {
        front_idx++;
    }
    if(back_idx - front_idx + 1 == capacity)
    {
        std::cerr &lt;&lt; "Queue full\n";
        return;
    }
    q_ptr[++back_idx] = value;
}

template&lt;typename T&gt;
void Queue&lt;T&gt;::pop()
{
    if(front_idx == -1)
    {
        std::cerr &lt;&lt; "Empty queue\n";
        return;
    }
    q_ptr[front_idx++] = T{};
}

template&lt;typename T&gt;
T Queue&lt;T&gt;::front() const
{
    return q_ptr[front_idx];
}

template&lt;typename T&gt;
T Queue&lt;T&gt;::back() const
{
    return q_ptr[back_idx];
}

template&lt;typename T&gt;
std::size_t Queue&lt;T&gt;::size() const
{
    return back_idx - front_idx + 1;
}

int main()
{
    Queue&lt;int&gt; q1{20};
    q1.pop();
    for(int i = 0; i &lt; 23; i++)
    {
        q1.push(i);
    }
    std::cout &lt;&lt; "Queue size: " &lt;&lt; q1.size() &lt;&lt; "\n";
    std::cout &lt;&lt; "Queue front: " &lt;&lt; q1.front() &lt;&lt; "\n";
    std::cout &lt;&lt; "Queue back: " &lt;&lt; q1.back() &lt;&lt; "\n";
    q1.pop();
    std::cout &lt;&lt; "Queue size: " &lt;&lt; q1.size() &lt;&lt; "\n";
    std::cout &lt;&lt; "Queue front: " &lt;&lt; q1.front() &lt;&lt; "\n";
    q1.pop();
    std::cout &lt;&lt; "Queue size: " &lt;&lt; q1.size() &lt;&lt; "\n";
    std::cout &lt;&lt; "Queue front: " &lt;&lt; q1.front() &lt;&lt; "\n";
    q1.push(12);
    std::cout &lt;&lt; "Queue size: " &lt;&lt; q1.size() &lt;&lt; "\n";
    std::cout &lt;&lt; "Queue back: " &lt;&lt; q1.back() &lt;&lt; "\n";
}



The attempt is to use unique_ptr as an array similar to a raw pointer. Is the usage and approach correct? Can it be improved?
As far as I know, I cannot use shared_ptr this way in C++11 or C++14. Is this the best approach with shared_ptr?
Yes I think your usage of unique_ptr is correct. The only recommendation I would make is to use make_unique for construction:

explicit Queue(std::size_t space) : 
    q_ptr(std::make_unique&lt;T[]&gt;(space)), 
    capacity(space) {}


This answer provides motivation for its use. I've also used explicit to avoid implicit conversion from size_t to Queue. And I made use of a member initialization list.

I wrote the following tests using googletest:

TEST(QueueTester, testEmpty) {
    Queue&lt;int&gt; queue(0);
    EXPECT_EQ(0, queue.size());
}

TEST(QueueTester, testSingle) {
    Queue&lt;int&gt; queue(1);
    EXPECT_EQ(0, queue.size());
    queue.push(0);
    EXPECT_EQ(1, queue.size());
}


But neither passed. I corrected this by using the following:

int front_idx = 0;


And changing push:

template&lt;typename T&gt;
void Queue&lt;T&gt;::push(T value)
{
    if (back_idx - front_idx + 1 == capacity)
    {
        std::cerr &lt;&lt; "Queue full\n";
        return;
    }
    q_ptr[++back_idx] = value;
}


I changed front and back to return references to the elements to allow the user to modify the contents. I also wrote overloads for returning const references to allow reading when the Queue is passed by const reference:

template&lt;typename T&gt;
T &amp;Queue&lt;T&gt;::front()
{
    return q_ptr[front_idx];
}

template&lt;typename T&gt;
T &amp;Queue&lt;T&gt;::back()
{
    return q_ptr[back_idx];
}

template&lt;typename T&gt;
const T &amp;Queue&lt;T&gt;::front() const
{
    return q_ptr[front_idx];
}

template&lt;typename T&gt;
const T &amp;Queue&lt;T&gt;::back() const
{
    return q_ptr[back_idx];
}


I further noticed that push, given a nonzero front_idx, will write past the end of the array pointed to by q_ptr. I took inspiration from this page and modified the code to store elements circularly around the array. Here is the final implementation:

#include &lt;iostream&gt;
#include &lt;memory&gt;
#include &lt;cstddef&gt;

template&lt;typename T&gt;
class Queue
{
    std::unique_ptr&lt;T[]&gt; q_ptr;
    int front_idx = 0;
    int back_idx = -1;
    std::size_t _size = 0;
    std::size_t capacity;
public:
    explicit Queue(std::size_t space) :
        q_ptr(std::make_unique&lt;T[]&gt;(space)),
        capacity(space)
    {
    }
    void push(T value);
    void pop();
    bool full();
    bool empty();
    T &amp;front();
    T &amp;back();
    const T &amp;front() const;
    const T &amp;back() const;
    std::size_t size() const;
};

template&lt;typename T&gt;
void Queue&lt;T&gt;::push(T value)
{
    if (full())
    {
        std::cerr &lt;&lt; "Queue full\n";
        return;
    }
    back_idx = (back_idx + 1) % capacity;
    q_ptr[back_idx] = value;
    ++_size;
}

template&lt;typename T&gt;
bool Queue&lt;T&gt;::full()
{
    return _size == capacity;
}

template&lt;typename T&gt;
void Queue&lt;T&gt;::pop()
{
    if (empty())
    {
        std::cerr &lt;&lt; "Empty queue\n";
        return;
    }
    q_ptr[front_idx] = T{};
    front_idx = (front_idx + 1) % capacity;
    --_size;
}

template&lt;typename T&gt;
bool Queue&lt;T&gt;::empty()
{
    return _size == 0;
}

template&lt;typename T&gt;
T &amp;Queue&lt;T&gt;::front()
{
    return q_ptr[front_idx];
}

template&lt;typename T&gt;
T &amp;Queue&lt;T&gt;::back()
{
    return q_ptr[back_idx];
}

template&lt;typename T&gt;
const T &amp;Queue&lt;T&gt;::front() const
{
    return q_ptr[front_idx];
}

template&lt;typename T&gt;
const T &amp;Queue&lt;T&gt;::back() const
{
    return q_ptr[back_idx];
}

template&lt;typename T&gt;
std::size_t Queue&lt;T&gt;::size() const
{
    return _size;
}