【问题标题】:Returning an 'any kind of input iterator' instead of a vector::iterator or a list::iterator [duplicate]返回“任何类型的输入迭代器”而不是 vector::iterator 或 list::iterator [重复]
【发布时间】:2010-09-12 17:22:34
【问题描述】:

假设我想在 C++ 中实现一个数据结构来存储有向图。由于 STL 容器,弧将存储在节点中。我希望用户能够以类似 STL 的方式迭代节点的弧。

我的问题是我不想在 Node 类(实际上是一个抽象基类)中公开我将在具体类中实际使用的 STL 容器。因此,我不想让我的方法返回 std::list::iterator 或 std::vector::iterator...

我试过这个:

class Arc;

typedef std::iterator<std::random_access_iterator_tag, Arc*> ArcIterator;  // Wrong!

class Node {
public:
  ArcIterator incomingArcsBegin() const {
    return _incomingArcs.begin();
  }
private:
  std::vector<Arc*> _incomingArcs;
};

但这是不正确的,因为 vector::const_iterator 不能用于创建 ArcIterator。那么这个 ArcIterator 到底是什么?

我发现了这篇关于Custom Iterators for the STL 的论文,但它没有帮助。我今天一定有点重... ;)

【问题讨论】:

标签: c++ stl iterator


【解决方案1】:

试试这个:

class Arc;
class Node {
private:
  std::vector<Arc*> incoming_;
public:
  typedef std::vector<Arc*>::iterator iterator;
  iterator incoming_arcs_begin()
  { return incoming_.begin(); }
};

并在其余代码中使用 Node::iterator。当/如果您更改容器时,您必须在一个地方更改 typedef。 (您可以更进一步,为存储添加额外的 typedef,在本例中为向量。)

至于 const 问题,要么将 vector 的 const_iterator 定义为你的迭代器,要么像 vector 一样定义双迭代器类型(const 和非 const 版本)。

【讨论】:

    【解决方案2】:

    看看 Adob​​e 的 any_iterator:这个类使用了一种称为 type erase 的技术,通过该技术,底层迭代器类型隐藏在抽象接口后面。注意:any_iterator 的使用会因虚拟调度而导致运行时损失。

    【讨论】:

      【解决方案3】:

      我想应该有一种方法可以通过直接的 STL 来做到这一点,类似于你正在尝试做的事情。

      如果没有,您可能需要考虑使用boost's iterator facades and adaptors,您可以在其中定义自己的迭代器或将其他对象调整为迭代器。

      【讨论】:

        【解决方案4】:

        要隐藏迭代器基于std::vector&lt;Arc*&gt;::iterator 的事实,您需要一个委托给std::vector&lt;Arc*&gt;::iterator 的迭代器类。 std::iterator 不会这样做。

        如果您查看编译器的 C++ 标准库中的头文件,您可能会发现 std::iterator 本身并不是很有用,除非您只需要一个为 iterator_category、@987654327 定义 typedef 的类@等

        正如 Doug T. 在他的回答中提到的,boost 库有一些类可以更容易地编写迭代器。特别是,如果您希望迭代器在取消引用时返回 Arc 而不是 Arc*,则 boost::indirect_iterator 可能会有所帮助。

        【讨论】:

          【解决方案5】:

          考虑使用Visitor Pattern 并反转关系:不是向图结构询问数据容器,而是给图一个函子,让图将该函子应用于其数据。

          访问者模式是图上常用的模式,请查看 boost 的有关访问者概念的图形库文档。

          【讨论】:

            【解决方案6】:

            如果您真的不希望该类的客户端知道它在下面使用了一个向量,但仍然希望他们能够以某种方式对其进行迭代,那么您很可能需要创建一个类来转发它的所有std::vector::iterator 的方法。

            另一种方法是根据它应该在下面使用的容器类型来模板化 Node。然后客户具体知道它使用的是什么类型的容器,因为他们告诉他们使用它。

            我个人认为将向量封装在远离用户的地方通常没有意义,但仍提供其大部分(甚至部分)接口。它的封装层太薄,无法真正提供任何好处。

            【讨论】:

              【解决方案7】:

              我查看了头文件VECTOR。

              vector<Arc*>::const_iterator
              

              是一个类型定义

              allocator<Arc*>::const_pointer
              

              那会是你的 ArcIterator 吗?喜欢:

              typedef allocator<Arc*>::const_pointer ArcIterator;
              

              【讨论】:

                【解决方案8】:

                您可以模板化 Node 类,并在其中 typedef iterator 和 const_iterator。

                例如:

                class Arc {};
                
                template<
                  template<class T, class U> class Container = std::vector,
                  class Allocator = std::allocator<Arc*>
                >
                class Node
                {
                  public:
                    typedef typename Container<Arc*, Allocator>::iterator ArcIterator;
                    typedef typename Container<Arc*, Allocator>::Const_iterator constArcIterator;
                
                    constArcIterator incomingArcsBegin() const {
                      return _incomingArcs.begin();
                    }
                
                    ArcIterator incomingArcsBegin() {
                      return _incomingArcs.begin();
                    }
                  private:
                    Container<Arc*, Allocator> _incomingArcs;
                };
                

                我没有尝试过这段代码,但它给了你想法。但是,您必须注意,使用 ConstArcIterator 只会禁止修改指向 Arc 的指针,而不是修改 Arc 本身(例如通过非 const 方法)。

                【讨论】:

                  【解决方案9】:

                  C++0x 将允许您使用 automatic type determination 执行此操作。

                  在新标准中,这个
                  for (vector::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr
                  可以换成这个
                  for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)

                  出于同样的原因,您将能够返回任何合适的迭代器,并将其存储在 auto 变量中。

                  在新标准生效之前,您必须对类进行模板化,或者提供一个抽象接口来访问列表/向量的元素。例如,您可以通过在成员变量中存储迭代器并提供成员函数来做到这一点,例如begin()next()。当然,这意味着一次只有一个循环可以安全地迭代您的元素。

                  【讨论】:

                  • 我认为这不会帮助我在不限制实现者选择容器的情况下定义我的接口。
                  • 是的。 auto 和模板一样,在编译时使用类型推导。在这里,为时已晚。可以在使用接口的代码之后编译实现。
                  【解决方案10】:

                  因为std::vector 保证有连续存储,所以这样做应该是完美的:

                  class Arc;
                  typedef Arc* ArcIterator;
                  
                  class Node {
                  public:
                      ArcIterator incomingArcsBegin() const {
                          return &_incomingArcs[0]
                      }
                  
                      ArcIterator incomingArcsEnd() const {
                          return &_incomingArcs[_incomingArcs.size()]
                      }
                  private:
                      std::vector<Arc*> _incomingArcs;
                  };
                  

                  基本上,指针的功能足以像随机访问迭代器一样,它们是足够的替代品。

                  【讨论】:

                    猜你喜欢
                    • 2016-04-21
                    • 2020-07-23
                    • 2022-08-11
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-06-07
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多