如何将这个旧的C ++双向链表代码更新到C ++ 11?

时间:2014-01-23 01:26:30

标签: c++ c++11 data-structures linked-list doubly-linked-list

我有以下代码,现在称为C++98

它实现了一个双向链表,我正在考虑将其更新为C++11,但我有以下问题:

  1. 似乎使用std::unique_ptr是有道理的,但在那里 仍需要其他指针“共享”unique_ptr。如果 nxt是unique_ptr,我该如何处理prviterator::it
  2. 似乎应该将分配器作为模板 paraemter,但它将是T类型的分配器,而不是LLNode?
  3. 是否有任何特征需要“注册”?
  4. 如果使用move语义,应该定义哪些方法,例如 operator=() &&等等?
  5. 需要纠正哪些未定义的行为(如果有的话)?
  6. 我不应该关心我应该做什么?
  7. 一个公认的答案会简要地讨论这些问题,然后在C++11重新实现(诚然,这是很多代码,但大多数都会被剪切和粘贴)

    一个好的答案将简要讨论这些问题,然后重新实现C++11最小数量,以说明如何应用这些概念

    使用标准的algorythms是有道理的,使用标准容器会破坏目的

    鼓励在有意义的地方使用C ++ 11功能。 foreach而不是仅使用lambdas,因为它可以正常工作

    这不是家庭作业

    代码:

    template <class T>
    class LinkedList
    {
        class LLNode
        {
        public:
            LLNode() // For Sentinel (requires that T has a default constructor)
            {}  // set prv/nxt directly
            explicit LLNode(const T& x) : data(x) // For normal nodes
            {}  // set prv/nxt directly
            T& get_data() const
            {
                return const_cast<T&>(data);
            }
            LLNode * prv;
            LLNode * nxt;
        private:
            T data;
        };
    public:
        class iterator
        {
        public:
            iterator(const LinkedList * p, LLNode * i)
                           : parent(const_cast<LinkedList *>(p)), it(i)
            {}
            iterator& operator ++() // pre
            {
                it = it->nxt;
                return *this;
            }
            iterator operator ++(int) // post
            {
                iterator ret=*this;
                it = it->nxt;
                return ret;
            }
            iterator& operator --() // pre
            {
                it = it->prv;
                return *this;
            }
            iterator operator --(int) //post
            {
                iterator ret=*this;
                it = it->prv;
                return ret;
            }
            T& operator *() const
            {
                return it->get_data();
            }
            T * operator ->() const
            {
                return &(it->get_data());
            }
            bool operator ==(const iterator& rhs) const
            {
                return it == rhs.it;
            }
            bool operator !=(const iterator& rhs) const
            {
                return it != rhs.it;
            }
            void erase()
            {
                parent->remove(it);
            }
            void insert_after(const T& x)
            {
                LLNode * add= new LLNode(x);
                parent->insert(it, add);
            }
            void insert_before(const T& x)
            {
                LLNode * add= new LLNode(x);
                parent->insert(it->prv, add);
            }
        private:
            LinkedList * parent;
            LLNode * it;
        };
        // Linked List class definition
        LinkedList()
        {
            init();
        }
        LinkedList(const LinkedList& rhs)
        {
            init();
            cp(rhs);
        }
        ~LinkedList()
        {
            rm();
        }
        LinkedList operator =(const LinkedList& rhs)
        {
            if (this != &rhs)
            {
                rm();
                cp(rhs);
            }
            return *this;
        }
        iterator begin() const
        {
            return iterator(this, sentinel.nxt);
        }
        iterator rbegin() const
        {
            return iterator(this, sentinel.prv);
        }
        iterator end() const
        {
            return iterator(this, const_cast<LLNode *>(&sentinel));
        }
        T& get_first() const  // illegal if is_empty() is true
        {
            return sentinel.nxt->get_data();
        }
        T& get_last() const // illegal if is_empty() is true
        {
            return sentinel.prv->get_data();
        }
        size_t size() const
        {
            return count;
        }
        bool is_empty() const
        {
            return count==0;
        }
        void insert_first(const T& x)
        {
            LLNode * add= new LLNode(x);
            insert(&sentinel, add);
        }
        void insert_last(const T& x)
        {
            LLNode * add= new LLNode(x);
            insert(sentinel.prv, add);
        }
        void erase_first() // illegal if is_empty() is true
        {
            remove(sentinel.nxt);
        }
        void erase_last() // illegal if is_empty() is true
        {
            remove(sentinel.prv);
        }
    private:
        void insert(LLNode * before, LLNode * added)
        {
            LLNode * after=before->nxt;
            added->prv=before;
            added->nxt=after;
            before->nxt=added;
            after->prv=added;
            ++count;
        }
        void remove(LLNode * node) // illegal if is_empty() is true
        {
            node->prv->nxt=node->nxt;
            node->nxt->prv=node->prv;
            delete node;
            --count;
        }
        void cp(const LinkedList& rhs)
        {
            for (iterator i=rhs.begin(); i != rhs.end(); ++i)
            {
                insert_last(*i);
            }
        }
        void rm()
        {
            LLNode * run=sentinel.nxt;
            while (run != &sentinel)
            {
                LLNode * dead=run;
                run=run->nxt;
                delete dead;
            }
        }
        void init()
        {
            count=0;
            sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
        }
        LLNode sentinel;
        size_t count;
    };
    

    编辑 - 基于Mooing Duck's answer的C ++ 11尝试:

    template <class T, class ALLOC=std::allocator<T> >
    class LinkedList
    {
            struct LLNode
            {
                    LLNode * prv;
                    LLNode * nxt;
                    T& get_data() { return data; }
                    T data;
            };
    public:
            class iterator
            {
            public:
                    using difference_type = ptrdiff_t;
                    using value_type = T;
                    using reference = T&;
                    using pointer = T*;
                    using iterator_category = std::bidirectional_iterator_tag;
    
                    iterator(LinkedList * p, LLNode * i) : parent(p), it(i)
                    {}
                    iterator& operator ++() // pre
                    {
                            it = it->nxt;
                            return *this;
                    }
                    iterator operator ++(int) // post
                    {
                            iterator ret=*this;
                            it = it->nxt;
                            return ret;
                    }
                    iterator& operator --() // pre
                    {
                            it = it->prv;
                            return *this;
                    }
                    iterator operator --(int) //post
                    {
                            iterator ret=*this;
                            it = it->prv;
                            return ret;
                    }
                    const T& operator *() const
                    {
                            return it->get_data();
                    }
                    T& operator *()
                    {
                            return it->get_data();
                    }
                    const T * operator ->() const
                    {
                            return &(it->get_data());
                    }
                    T * operator ->()
                    {
                            return &(it->get_data());
                    }
                    bool operator ==(const iterator& rhs) const
                    {
                            return it == rhs.it;
                    }
                    bool operator !=(const iterator& rhs) const
                    {
                            return it != rhs.it;
                    }
                    void erase()
                    {
                            parent->remove(it);
                    }
                    void insert_after(T& x)
                    {
                            auto add=parent->alloc_node(x);
                            parent->insert(it->nxt, add);
                    }
                    void insert_before(T& x)
                    {
                            auto add=parent->alloc_node(x);
                            parent->insert(it, add);
                    }
            private:
                    LinkedList * parent;
                    LLNode * it;
            };
            class const_iterator
            {
            public:
                    using difference_type = ptrdiff_t;
                    using value_type = const T;
                    using reference = const T&;
                    using pointer = const T*;
                    using iterator_category = std::bidirectional_iterator_tag;
    
                    const_iterator(const LinkedList * p, const LLNode * i) : parent(p),
                            it(const_cast<LLNode *>(i))
                    {}
                    const_iterator(iterator& cvt) : parent(cvt.parent), it(cvt.it)
                    {}
                    const_iterator& operator ++() // pre
                    {
                            it = it->nxt;
                            return *this;
                    }
                    const_iterator operator ++(int) // post
                    {
                            const_iterator ret=*this;
                            it = it->nxt;
                            return ret;
                    }
                    const_iterator& operator --() // pre
                    {
                            it = it->prv;
                            return *this;
                    }
                    const_iterator operator --(int) //post
                    {
                            const_iterator ret=*this;
                            it = it->prv;
                            return ret;
                    }
                    const T& operator *() const
                    {
                            return it->get_data();
                    }
                    const T * operator ->() const
                    {
                            return &(it->get_data());
                    }
                    bool operator ==(const const_iterator& rhs) const
                    {
                            return it == rhs.it;
                    }
                    bool operator !=(const const_iterator& rhs) const
                    {
                            return it != rhs.it;
                    }
            private:
                    const LinkedList * parent;
                    LLNode * it;
            };
    
            using my_alloc=typename
                 std::allocator_traits<ALLOC>::template rebind_alloc<LLNode>;
    
            using my_traits=typename
                 std::allocator_traits<ALLOC>::template rebind_traits<LLNode>;
    
            // Linked List class definition
            LinkedList(const ALLOC& alloc = ALLOC() ) : mem(alloc)
            {
                    init();
            }
            LinkedList(const LinkedList& rhs) : mem(rhs.mem)
            {
                    init();
                    cp(rhs);
            }
            LinkedList(LinkedList&& rhs) : mem(rhs.mem) // Move
            {
                    init();
                    shallow_cp(rhs);
    
            }
            ~LinkedList()
            {
                    rm();
            }
            LinkedList operator =(const LinkedList& rhs)
            {
                    if (this != &rhs)
                    {
                            rm();
                            cp(rhs);
                    }
                    return *this;
            }
            LinkedList operator =(LinkedList&& rhs) // Move
            {
                    if (this != &rhs)
                    {
                            rm();
                            shallow_cp(rhs);
                    }
                    return *this;
            }
            const_iterator begin() const
            {
                    return const_iterator(this, sentinel.nxt);
            }
            iterator begin()
            {
                    return iterator(this, sentinel.nxt);
            }
            const_iterator rbegin() const
            {
                    return const_iterator(this, sentinel.prv);
            }
            iterator rbegin()
            {
                    return iterator(this, sentinel.prv);
            }
            const_iterator end() const
            {
                    return const_iterator(this, &sentinel);
            }
            iterator end()
            {
                    return iterator(this, &sentinel);
            }
            T& front()  // illegal if is_empty() is true
            {
                    return sentinel.nxt->get_data();
            }
            T& back() // illegal if is_empty() is true
            {
                    return sentinel.prv->get_data();
            }
            size_t size() const
            {
                    return count;
            }
            bool is_empty() const
            {
                    return count==0;
            }
            void insert_first(const T& x)
            {
                    LLNode * add=alloc_node(x);
                    insert(&sentinel->nxt, add);
            }
            void insert_last(const T& x)
            {
                    LLNode * add=alloc_node(x);
                    insert(&sentinel, add);
            }
            void erase_first() // illegal if is_empty() is true
            {
                    remove(sentinel.nxt);
            }
            void erase_last() // illegal if is_empty() is true
            {
                    remove(sentinel.prv);
            }
    private:
            LLNode * alloc_node(const T& x)
            {
                    auto ret = my_traits::allocate(mem,1);
                    my_traits::construct(mem, &(ret->data), x);
                    return ret;
            }
            void unalloc_node(LLNode * dead)
            {
                    my_traits::deallocate(mem, dead, 1);
            }
            void insert(LLNode * after, LLNode * added)
            {
                    LLNode * before=after->prv;
                    added->prv=before;
                    added->nxt=after;
                    before->nxt=added;
                    after->prv=added;
                    ++count;
            }
            void remove(LLNode * node) // illegal if is_empty() is true
            {
                    node->prv->nxt=node->nxt;
                    node->nxt->prv=node->prv;
                    unalloc_node(node);
                    --count;
            }
            void cp(const LinkedList& rhs)
            {
                    mem = rhs.mem;
                    for (const_iterator i=rhs.begin(); i != rhs.end(); ++i)
                    {
                            insert_last(*i);
                    }
            }
            void shallow_cp(LinkedList& rhs)
            {
                    if (rhs.count)
                    {
                            count=rhs.count;
                            sentinel=rhs.sentinel; // shallow copy
    
                            // fix the links to the old sentinel
    
                            sentinel.nxt.prv=&sentinel;
                            sentinel.prv.nxt=&sentinel;
                            rhs.init();
                    }
            }
    
            void rm()
            {
                    LLNode * run=sentinel.nxt;
                    while (run != &sentinel)
                    {
                            LLNode * dead=run;
                            run=run->nxt;
                            unalloc_node(dead);
                    }
            }
            void init()
            {
                    count=0;
                    sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
            }
            LLNode sentinel;
            size_t count;
            my_alloc mem;
    };
    

    有什么遗漏/错误吗?

1 个答案:

答案 0 :(得分:2)

  

似乎使用std :: unique_ptr是有道理的,但仍需要其他指针“共享”unique_ptr。即如果nxt是unique_ptr,我该如何处理prv和iterator :: it?

别。 (1)它使各种内部算法更难以执行而不会意外地删除节点,并且(2)unique_ptr存储删除器,在你的情况下是(A)迭代器的副本,或(B)指向迭代器的指针。任何一个都是浪费空间。容器应存储分配器,容器应处理删除。

  

它似乎应该将分配器作为模板paraemter,但它将是T类型的分配器,而不是LLNode?

容器采用类型为T的分配器,尽管它们都在内部使用了反弹类型。标准是容器的分配器采用类型T,这样每个std::vector<T, allocator<?>>都匹配。此外,外人不应该能够访问LLNode。你可能会在内部存储一个given_allocator<LLNode>。这就是我们重新绑定的原因。

  

是否有任何特征需要“注册”?

对于容器,没有。有匹配的接口,但那些相对明显。

是的,你的迭代器应该注册特征,但只需在前面添加五个typedef即可轻松完成。

typedef ptrdiff_t difference_type; //usually ptrdif_t
typedef T value_type; //usually T
typedef T& reference; //usually T&
typedef T* pointer; //usually T*
typedef std::bidirectional_iterator_tag iterator_category;
  

如果使用移动语义,应该定义哪些方法,例如operator =()&amp;&amp;等?

显然,容器应该是可构造的,并且如果有意义的话可以移动分配,它在99.99%的时间内完成。甚至std::array也有移动运算符。还要确定哪些成员函数应该支持move-only-T(通过迭代器插入范围,但不插入范围+计数),以及哪些成员函数支持任何 T(emplace one)。

  

需要纠正哪些未定义的行为(如果有的话)?

  • 当标准是在迭代器之前插入时,您的insert(iterator, data)会在迭代器之后插入数据。您的方式使得无法在开头添加数据,并且可以在后端添加数据。

  • 您的迭代器有但不需要removeinsert函数。我不会提到它,但在这种情况下,它们需要迭代器的大小是所需的两倍。我会提出一个暂时的建议来删除它们,但只是试探性的。这是一个小小的惩罚,可能是一个有用的功能。

  • 您的operator =取消分配所有内容,然后重新分配。在可能的情况下简单地逐个元素跳过复制可能很方便。尽管如此诡计多端。

  • 您缺少从一对迭代器构造的构造函数。

  • 您的迭代器允许变更来自const容器的数据。这一件事很难

  • get_firstget_last通常称为frontback

  

我不应该关心我应该做什么?

每个人都忽视的最大问题是异常安全。您的代码异常安全,但这似乎是因为您的代码非常简单,并且您跳过了所有“硬”部分:P

有关更多信息,请查看以下内容:
Writing your own STL Container
How to implement an STL-style iterator and avoid common pitfalls?


编辑您的C ++ 11后续内容:

缺少:

  • 模板LLNode(U&amp; x)//或,一个T&&和一个const T&
  • LinkedList(std :: initializer_list x)
  • T&安培; LLNode :: get_data()const //重要,你的代码甚至不编译
  • T * iterator :: operator-&gt;()const // important
  • const_iterator cbegin()const
  • const_iterator cend()const
  • const T&amp; front()const // important
  • const T&amp; back()const // important
  • template void assign(iter,iter);
  • void assign(std :: initializer_list);
  • void swap(const X&amp;);

不正确:

  • insert_ *应该使用U&&并使用std::forward<U>(x)。变种模板会更好。
  • 没有迭代器对构造函数。
  • alloc_node中的内存泄漏,你应该构建一个LLNode,你有未初始化的prv / nxt。
  • unalloc_node永远不会破坏,只会解除分配,导致内存泄漏

缺少但非常不重要

  • typedef std::reverse_iterator<iterator> reverse_iterator;
  • typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
  • reverse_iterator rbegin(); //可选
  • const_reverse_iterator rbegin()const;
  • const_reverse_iterator crbegin()const;
  • reverse_iterator rend(); //可选
  • const_reverse_iterator rend()const;
  • const_reverse_iterator crend()const;

可选:

  • 不确定为什么my_allocmy_traits并且它们相同。
  • 从分配器私下继承。它有点奇怪,但通常可以节省8个字节。