在迭代器中包装链表

时间:2010-08-13 16:58:03

标签: c++ boost iterator shared-ptr

我经常使用的一组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引用?

  3. 谢谢, PaulH


    编辑:我正在包装的链接列表API不是我的。我无法改变它们。

5 个答案:

答案 0 :(得分:4)

首先,当然,对于实际使用,你几乎肯定不应该编写自己的链表或迭代器。其次,链接列表的好用(即使已经编写,调试过的等等)也很少见 - 除非在一些相当不寻常的情况下,你应该使用别的东西(最常用的是矢量)。

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

// 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;
};

要与标准库中的算法一起使用,您必须定义比尝试的更多(例如,typedef,如value_type和reference_type)。这只是为了显示一般结构。

答案 1 :(得分:3)

我的建议:对此进行废弃并使用现有的slist<>实施。 IIRC,它将使用C ++ 1x,因此您的编译器可能已经支持它。或者它可能在boost中。或者从其他地方拿走它。

<击>

无论你从哪里得到它,你得到的所有 这些问题已经解决了 ,很可能 经过充分测试 ,因此 无错误 快速 ,使用它的代码很容易 可识别的 (看着我们很多人会立即看到它的作用,因为它已经存在了一段时间,它将成为下一个标准的一部分)。

我最后一次编写自己的列表类是在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迭代器也应该具有可比性),但这并不难添加到此。

答案 2 :(得分:1)

根据您的说法,您可能使用struct SomeObject类型的BIG遗留代码,但您希望使用新代码并使用迭代器/ 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。:如果您处于某种带有这种结构的遗留代码的情况下,我也会这样做,并且我实现了这种解决方法。它很棒。

答案 3 :(得分:0)

  1. 您可以MyObjectIterator成为MyObject的朋友。我没有看到任何更好的方法。而且我认为迭代器能够获得他们履行职责所需的特殊访问权限是合理的。

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

  3. 我可以看到为什么你试图设计这个方案,但是让单独的MyObjects指向SomeObjects甚至通过SomeObjects本身构成真正的列表结构....这不是一个简单的方法来包装列表。 / p>

    最简单的替代方法是完全取消MyObject。让你的迭代器直接对SomeObject实例起作用,并在解除引用时返回它们。当然确实会将SomeObject暴露给外部,特别是其next成员。但这真的是一个足够大的问题来证明一个更复杂的方案能够将它全部包起来吗?

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

    我能想到的最后一个选择就是在通过此API提供给您之后将数据传输到更方便的数据结构列表中。然后当然只有在必要时才将其传回原始SomeObject列表以将其传递回API。只需创建自己的SomeObjectData或其他任何内容来存储字符串并将其放在std::list中。这对您来说实际上是否可行实际上取决于您在上述API的上下文中如何使用此数据。如果有其他API函数修改SomeObject列表并且您需要经常使用它们,那么不断地将SomeObject列表转换为std::list<SomeObjectData>和从{{1}}转换可能会很烦人。

答案 4 :(得分:0)

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

在让我们检查要求之前充满责任。

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

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

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

实现很简单,即使iterator一般没有正确实现迭代器特性,这个页面也足够了,但我会考虑完全抛弃这个想法并转向更好的方案:

  • class MyObject { public: ... private: SomeObject mData; };
  • 结束GetObjectList并返回deque<MyObject>,我猜LONG会返回项目数吗?