【问题标题】:How to iterate through a container in another class?如何遍历另一个类中的容器?
【发布时间】:2010-11-11 16:07:42
【问题描述】:

我遇到过几次这个问题,想知道是否有一个简单的方法或模式可以用来解决它。

想象一个树结构,其中每个节点都包含一个指向子节点的 STL 容器(向量)。我希望客户端代码能够遍历这棵树,并遍历子容器以访问其他部分。

我的问题是我想保持对节点的封装,同时让客户端轻松查看该节点的所有子节点。我还想确保如果客户端获得对树中根节点的 const 引用,那么对树的后续部分的所有访问也是 const。

我应该尝试为我的节点类型创建一个迭代器类,让一个节点方法返回向量迭代器,还是我缺少更优雅的模式?

编辑:我想再次强调,虽然我看到了一些好的想法,但我有指向其他节点的指针容器。返回 vector<node *>::const_iterator 不会阻止客户端在节点上调用非常量方法。它只保护指针本身不指向不同的对象。

【问题讨论】:

    标签: c++ design-patterns stl tree iterator


    【解决方案1】:

    这样的事情通常就足够了:

    class node 
    {
    public:
        typedef std::vector<node> node_list;
        typedef node_list::const_iterator const_iterator;
    
        const_iterator begin() const { return children_.begin(); }
        const_iterator end() const { return children_.end(); }
    
    private:
        node_list children_;
    }
    

    这允许您更改底层容器类型,而无需更改迭代 node 的子级的代码。

    这确实有一个缺点,它会泄露一个实现细节,因为使用你的 node::const_iterator 的代码知道它是一个随机访问迭代器(因为 std::vector:: const_iterator 是一个随机访问迭代器),所以你可能很难切换到不支持随机访问的容器。

    如果您希望能够自己控制迭代器类别,您可能需要创建自己的 iterator 类,以提供您想要提供的确切行为。

    【讨论】:

    • 如果 node_list 是指向节点的指针向量,并且我希望客户端只有指向 const 节点的指针,这是否也有效?
    • “代码知道它是一个随机访问迭代器”有趣的一点,但同样代码也知道它专门是一个vector::const_iterator。然而,不假思索地使用operator+ 比不假思索地将迭代器分配给vector&lt;node&gt;::const_iterator 而不是node::const_iterator 要容易得多。我想知道是否存在迭代器适配器除了将其包装到特定类别的迭代器“降级”之外什么都不做的情况。然后你可以返回make_forward_iterator(children_.begin()),或者类似的。
    • @Steve:由于类别是迭代器类型的一部分,您必须在 typedef 中进行转换。我认为您可以使用template &lt;RealIteratorT&gt; class forward_iterator_wrapper : private RealIteratorT { }; 轻松做到这一点。可能有问题;我还没有深入考虑。
    【解决方案2】:

    这不是您问题的直接答案,而是另一种方法。您可以通过注册回调函子来获取control inversion,而不是让客户端代码运行该节目。例如:

    // Derive from this class to create a visitor
    class AbstractVisitor
    {
    public:
        virtual void operator() (const T &) = 0;
    };
    
    
    // Your recursive data-structure class
    class MyClass
    {
    public:
        void walk(AbstractVisitor &v) const
        {
            // Call the client callback
            v(payload);
            for (std::vector<MyClass>::const_iterator it = children.begin();
                 it != children.end(); ++it)
            {
                // Recurse
                it->walk(v);
            }
        }
    
    private:
        T payload;   // Some sort of payload associated with the class
        std::vector<MyClass> children;
    };
    
    
    // You could have different visitor classes to do different things
    class MyVisitor : public AbstractVisitor
    {
    public:
        virtual void operator() (const T &t)
        {
            // Do something with t
        }
    }
    
    
    int main()
    {
        MyClass m;
        MyVisitor v;
        ...
        m.walk(v);
    }
    

    完成封装!

    【讨论】:

    • 访客模式! :-O(好吧,至少与它相关。)
    • 就个人而言,在 C++ 中,我希望 walk 成为带有仿函数参数的函数模板。如果做不到这一点(因为有时您不希望模板位于接口边界),我希望用户数据指针与我的函数指针一起使用,或者与虚函数的接口一起使用。否则 walk 仅限于除了全局状态之外没有状态的操作,并且不返回任何值(因为它是 void)。也就是说,设计不佳的;-)
    • @Steve:是的,绝对的;理想情况下,您希望能够为其提供函子。但这对于上面的代码 sn-p 来说似乎有点复杂!
    • std::function&lt;void(T)&gt; 可以工作,具有虚拟operator() 重载的访问者基类也可以。
    • @James:那是 C++0x 模板吗?
    【解决方案3】:

    为了遍历整个树,您必须创建自己的迭代器类,对于仅遍历子节点,您可以安全地返回向量迭代器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-15
      • 2021-05-25
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      • 2013-11-04
      • 2010-10-06
      • 2020-10-16
      相关资源
      最近更新 更多