极简单的单线程队列

template<typename T>
class Queue
{
private:
    struct Node
    {
        T                  data;
        std::unique_ptr<T> next = nullptr;
        Node(T _data):
            data(std::move(_data))
        {}
    };

    std::unique_ptr<Node> head = nullptr;
    Node*                 tail = nullptr;
public:
    Queue()                        = default;
    Queue(const Queue&)            = delete;
    Queue& operator=(const Queue&) = delete;

    std::shared_ptr<T> tryPop()
    {
        if (head == nullptr){
            return std::shared_ptr<T>();
        }

        auto const result  = std::make_shared<T>(std::move(head->data));
        auto const oldHead = std::move(head);
        head = std::move(oldHead->next);
        return result;
    }

    void push(T newVal)
    {
        auto newNextNode = std::make_unique<Node>(std::move(newVal));
        auto newTail = newNextNode.get ();
        if (tail != nullptr){
            tail->next = std::move(newNextNode);
        }
        else{
            head = std::move(newNextNode);
        }
        tail = newTail;
    }

};

我也曾怀疑,为何不把 tail 也作为一个 unique_ptr,所以使用了一个反证法。若 tail 的类型是一个 unique_ptr,那么当 push 一个新值的时候,接下来的操作应该是:

auto newTail = make_unique<T>(newValue);
tail->next = move(newTail);
tail = move(newTail); // oops!

问题就出在第三行。怎么说呢,如果是拷贝语义,那么是没错的,然而使用的是 move,这样在使用了第一个 move 操作之后,newTail 就会被析构,第二个 move 移动的是一个 nullptr。

当然,如果全部使用裸指针倒是可以这样:

auto newTail = new Node(newValue); // 新结点
tail->next = newTail;              // 新节点成为现 tail 的 next    
tail = newTail;                    // 让 tail 指向真正的新节点

倒是达到了效果,但是对于其资源的释放又成了一个棘手的问题,因此把 head 和 tail 的类型分别设为 unique_ptr 和  Node* 就有意义了。添加元素时,为了新节点的信息得以保存,先根据添加的值构造一个 unique_ptr 来代替原先 tail 的 next,又因为在使用 move 移动这个 unique_ptr 之后,其本身会被析构,所以在代替之前,需要先使用 get 得到新链表节点的信息然后再在最后让 tail 指向真正的队尾。

 
原文地址:https://www.cnblogs.com/wuOverflow/p/4832256.html