【问题标题】:Wrapping linked lists in iterators在迭代器中包装链表
【发布时间】:2011-03-29 14:09:37
【问题描述】:

我常用的一组 API 遵循链表模式:

struct SomeObject
{
    const char* some_value;
    const char* some_other_value;
    SomeObject* next;    
}

LONG GetObjectList( SomeObject** list );
void FreeObjectList( SomeObject* list );

此 API 不是我的,我无法更改。

所以,我想封装它们的构造/销毁、访问并添加迭代器支持。我的计划是做这样的事情:

/// encapsulate access to the SomeObject* type
class MyObject
{
public:
    MyObject() : object_( NULL ) { };
    MyObject( const SomeObject* object ) : object_( object ) { };
    const char* SomeValue() const 
    { 
        return NULL != object_ ? object_->some_value : NULL; 
    };
    const char* SomeValue() const 
    { 
        return NULL != object_ ? object_->some_other_value : NULL; 
    };

private:
    SomeObject* object_;
}; // class MyObject

bool operator==( const MyObject& i, const MyObject& j )
{
    return // some comparison algorithm.
};

/// provide iterator support to SomeObject*
class MyObjectIterator 
    : public boost::iterator_adaptor< MyObjectIterator, 
                                      MyObject*,
                                      boost::use_default,
                                      boost::forward_traversal_tag >
{
public:
    // MyObjectIterator() constructors

private:
    friend class boost::iterator_core_access;

    // How do I cleanly give the iterator access to the underlying SomeObject*
    // to access the `next` pointer without exposing that implementation detail
    // in `MyObject`?
    void increment() { ??? }; 
};

/// encapsulate the SomeObject* creation/destruction
class MyObjectList
{
public:
    typedef MyObjectIterator const_iterator;

    MyObjectList() : my_list_( MyObjectList::Create(), &::FreeObjectList )
    {
    };

    const_iterator begin() const
    {
        // How do I convert a `SomeObject` pointer to a `MyObject` reference?
        return MyObjectIterator( ??? );
    };

    const_iterator end() const
    {
        return MyObjectIterator();
    };

private:
    static SomeObject* Create()
    {
        SomeObject* list = NULL;
        GetObjectList( &list );
        return list;
    };

    boost::shared_ptr< void > my_list_;
}; // class MyObjectList

我的两个问题是:

  1. 如何干净利落地让MyObjectIterator 访问底层SomeObject 以访问链表中的next 指针,而不暴露MyObject 中的实现细节?

  2. MyObjectList::begin() 中,如何将SomeObject 指针转换为MyObject 引用?

谢谢, 保罗H


编辑:我包装的链表 API 不是我的。我无法更改它们。

【问题讨论】:

  • std::list 不适合你?
  • @Nikolai - 链表不是我的。我无法改变它。

标签: c++ boost iterator shared-ptr


【解决方案1】:

首先,当然,对于实际使用,您几乎可以肯定根本不应该编写自己的链表或迭代器。其次,链表的良好用途(即使是已经编写、调试过等的链表)也非常少见——除了在一些相当不寻常的情况下,您可能应该使用其他东西(最常见的是向量)。

也就是说,迭代器通常是它提供访问权限的类的朋友(或嵌套类)。它为世界其他地方提供了一个抽象接口,但迭代器本身直接了解(和访问)它提供访问的链表(或任何容器)的内部结构。这是一个一般概念:

// warning: This is really pseudo code -- it hasn't been tested, and would 
// undoubtedly require a complete rewrite to even compile, not to mention work.
template <class T>
class linked_list { 

public:
    class iterator;

private:
    // A linked list is composed of nodes.
    // Each node has a value and a pointer to the next node:    
    class node { 
        T value;
        node *next;
        friend class iterator;
        friend class linked_list;
    public:
        node(T v, node *n=NULL) : value(v), next(n) {}
    };

public:

    // An iterator gives access to the linked list.
    // Operations: 
    //      increment: advance to next item in list
    //      dereference: access value at current position in list
    //      compare: see if one iterator equals another    
    class iterator { 
        node *pos;
    public:
        iterator(node *p=NULL) : pos(p) {}
        iterator operator++() { 
            assert(pos); 
            pos = pos->next; 
            return *this;
        }
        T operator*() { return pos->value; }
        bool operator!=(iterator other) { return pos != other.pos; }
    };

    iterator begin() { return iterator(head); }
    iterator end()   { return iterator(); }

    void push_front(T value) { 
        node *temp = new node(value, head); 
        head = temp;
    }

    linked_list() : head(NULL) {}

private:
    node *head;
};

要与标准库中的算法一起工作,您必须定义比这更多的东西(例如,像 value_type 和 reference_type 这样的类型定义)。这只是为了显示一般结构。

【讨论】:

    【解决方案2】:

    我的建议:丢弃它并使用现有的slist&lt;&gt; 实现。 IIRC,它将在 C++1x 中,因此您的编译器可能已经支持它。或者它可能在boost。或者从别的地方拿走。

    无论你从哪里得到它,你得到的都是这些问题已经解决了,很可能经过很好的测试,因此 没有错误fast,并且使用它的代码很容易 recognizable(我们中的许多人看到它就会立即看到它的作用,因为它已经存在了一段时间,并且将成为下一个标准的一部分)。

    我最后一次编写自己的列表类是在 STL 成为 C++ 标准库的一部分之前。

    好的,既然你已经被你所拥有的 API 困住了,这里有一些东西可能会让你开始:

    class MyObjectList
    {
    public:
        typedef SomeObject value_type;
        // more typedefs
    
        class iterator {
        public:
            typedef SomeObject value_type;
            // more typedefs
    
            iterator(SomeObject* pObj = NULL)
                                        : pObj_(pObj) {}
            iterator& operator++()        {if(pObj_)pObj_ = pObj_->next;}
            iterator  operator++(int)     {iterator tmp(*this);
                                          operator++();
                                          return tmp;}
            bool operator==(const iterator& rhs) const
                                          {return pObj_ == rhs.pObj_;}
            bool operator!=(const iterator& rhs) const
                                          {return !operator==(rhs);}
                  value_type& operator*() {return pObj_;}
        private:
            SomeObject* pObj_;
        };
    
        class const_iterator {
        public:
            typedef SomeObject value_type;
            // more typedefs
            const_iterator(const SomeObject* pObj = NULL)
                                        : pObj_(pObj) {}
            iterator& operator++()        {if(pObj_)pObj_ = pObj_->next;}
            iterator  operator++(int)     {iterator tmp(*this);
                                          operator++();
                                          return tmp;}
            bool operator==(const iterator& rhs) const
                                          {return pObj_ == rhs.pObj_;}
            bool operator!=(const iterator& rhs) const
                                          {return !operator==(rhs);}
            const value_type& operator*() {return pObj_;}
        private:
            const SomeObject* pObj_;
        };
    
        MyObjectList()                   : list_() {GetObjectList(&list_;);}
        ~MyObjectList()                    {FreeObjectList(list_);}
              iterator begin()             {return list_ ?       iterator(list_)
                                                         :       iterator();}
        const_iterator begin() const       {return list_ ? const_iterator(list_)
                                                         : const_iterator();}
              iterator end  ()             {return       iterator(getEnd_());}
        const_iterator end  () const       {return const_iterator(getEnd_());}
    
    private:
        SomeObject* list_;
    
        SomeObject* getEnd_()
        {
            SomeObject* end = list_;
            if(list_)
                while(end->next)
                    end = end->next;
            return end;
        }
    };
    

    显然,这还不止于此(例如,我认为 const 和非 const 迭代器也应该具有可比性),但这应该不难补充。

    【讨论】:

    • 我在问题陈述中并不清楚。我包装的 API 不是我的,我无法将它们重新实现为链表。
    【解决方案3】:

    根据您所说,您可能有一个使用 struct SomeObject 类型的大型遗留代码,但您想很好地使用新代码并使用迭代器/stl 容器。

    如果是这种情况,您将无法(以一种简单的方式)在所有遗留代码库中使用新创建的迭代器,因为这将更改大量代码,但是,您可以编写模板化迭代器也就是说,如果您的 structs 遵循相同的模式,具有 next 字段,则将起作用。

    类似这样的东西(我没有测试也没有编译过,这只是一个想法):

    假设你有你的结构:

    struct SomeObject
    {
        SomeObject* next;
    }
    

    你将能够创建这样的东西:

    template <class T>
    class MyIterator {
    public:
    //implement the iterator abusing the fact that T will have a `next` field, and it is accessible, since it's a struct
    };
    
    template <class T>
    MyIterator<T> createIterator(T* object) {
       MyIterator<T> it(object);
       return it;
    }
    

    如果你正确地实现了你的迭代器,你将能够在你的旧结构中使用所有的 STL 算法。

    PS.:如果您在使用这种结构的一些遗留代码的场景中,我也这样做,并且我实现了这个解决方法。效果很好。

    【讨论】:

      【解决方案4】:
      1. 你可以让MyObjectIterator 成为MyObject 的朋友。我看不出有什么更好的办法。实际上,我认为迭代器获得他们履行职责所需的任何特殊朋友访问权限是合理的。

      2. 您似乎没有考虑您的MyObject 实例将如何以及在何处存储。或许这就是这个问题的由来。似乎您必须在 MyObjectList 中有一个单独的 MyObjects 链接列表。然后至少MyObjectList::begin() 可以获取它们的内部链接列表的第一个MyObject。如果唯一可能修改或重新排列此列表的操作仅通过您提供的迭代器发生,那么您可以让这些列表保持同步而不会有太多麻烦。否则,如果您使用的 API 中有函数获取原始的 SomeObject 链接列表并对其进行操作,那么您可能会遇到麻烦。

      我明白您为什么尝试设计此方案,但是即使通过 SomeObjects 本身也指向 SomeObjects 的单独 MyObjects 构成了真正的列表结构....这不是包装列表的简单方法。

      最简单的选择就是完全取消 MyObject。让您的迭代器直接针对 SomeObject 实例工作,并在取消引用时返回这些实例。当然,这确实将 SomeObject 暴露给外部,尤其是它的next 成员。但是,这真的是一个足够大的问题,足以证明一个更复杂的方案是合理的吗?

      另一种处理方式可能是让MyObject 私下继承SomeObject。然后每个 SomeObject 实例可以被向下转换为对 MyObject 实例的引用,并以这种方式提供给外部世界,从而隐藏 SomeObject 的实现细节,只公开所需的公共成员函数。该标准可能不保证这会起作用,但在实践中,因为它们可能具有完全相同的内存布局,您可能能够摆脱它。但是,除非绝对必要,否则我不确定我是否真的会在实际程序中尝试这样的事情。

      我能想到的最后一个选择是,在这个 API 提供给你之后,将数据传输到更方便的数据结构列表中。然后当然只有在必要时才将其传输回原始 SomeObject 列表,然后将其传回 API。只需制作您自己的SomeObjectData 或其他任何东西来存储字符串并将它们放入std::list。这对您来说是否真的可行取决于如何在您提到的 API 的上下文中使用这些数据。如果有其他 API 函数可以修改 SomeObject 列表并且您需要经常使用它们,那么不断将 SomeObject 列表与std::list&lt;SomeObjectData&gt; 相互转换可能会很烦人。

      【讨论】:

        【解决方案5】:

        到目前为止,我已经看到了一些非常好的答案,但我担心它们可能是“赤裸裸的”。

        在盲目收费之前,让我们检查一下要求。

        • 您注意到该结构类似于单链表,C++0x 标准定义了这样的结构,称为forward_list。阅读它的界面,你的应该很接近。
        • 迭代器类型将为 ForwardIterator。

        不过,我想揭露一个非常烦人的事实:谁负责记忆?

        我很担心,因为您提供的接口中没有copy 工具,所以我们是否应该禁用新列表类的复制构造函数和赋值运算符?

        实现很简单,尽管iterator 通常没有正确实现迭代器特征,但这个页面已经足够了,但我会考虑完全放弃这个想法并转向更好的方案:

        • class MyObject { public: ... private: SomeObject mData; };
        • 结束GetObjectList 并返回deque&lt;MyObject&gt;,我猜LONG 返回项目数?

        【讨论】:

        • LONG 实际上返回一个错误代码。当next 为NULL 时,您位于最后一项。我需要指定特殊的复制语义吗? MyObject 仅包含一个指针,因此默认副本应该可以工作。 MyObjectIterator 的复制语义应该在boost::iterator_adaptor 中实现。 MyObjectList 仅包含 boost::shared_ptr&lt;&gt;,因此默认副本也应该适用。还是我误解了什么?
        • 您在说哪种解决方案?接口重写还是完全封装+STL容器?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-26
        • 2012-09-07
        • 2016-03-02
        • 2016-01-09
        • 2018-10-06
        • 2016-07-08
        • 1970-01-01
        相关资源
        最近更新 更多