自制迭代器的正确性

时间:2015-06-08 14:07:48

标签: c++ iterator const-iterator

一般目标

我管理了一组对象(Collection Real作为简单示例)。然后我在我的集​​合上定义了迭代器。这意味着:iteratorconst_iteratorreverse_iteratorconst_reverse_iterator。在这个例子中,我只关注iteratorconst_iterator,其他两个非常相似。

之后,我想在我的集合中定义一个过滤器,它根据特定条件保留或不保留元素。例如,仅保留具有正值的Real个实例。我也想在保留的元素上迭代我的集合。

我如何实施集合

对于这个例子,我在集合中的对象非常简单。目标只是拥有一个对象而不是本机类型:

struct Real
{
    public:
      double r;
};

然后我定义我的收藏而不必知道里面的真实容器:

class Collection
{
  public:
    typedef std::vector<Real>::iterator iterator;
    typedef std::vector<Real>::const_iterator const_iterator;
  private:
    std::vector<Real> data;
  public:
    Collection() : data() {}
    Collection(unsigned long int n) : data(n) {}
    Collection(unsigned long int n, const Real& x) : data(n,x) {}
    Collection::iterator       begin()       { return this->data.begin(); }
    Collection::iterator       end()         { return this->data.end(); }
    Collection::const_iterator begin() const { return this->data.begin(); }
    Collection::const_iterator end() const   { return this->data.end(); }
};

在这个简单的例子中,这非常有效:

int main()
{
  Collection c(5);
  double k = 1.0;
  for(Collection::iterator it = c.begin(); it != c.end(); ++it)
  {
    it->r = k;
    k *= -2.0;
  }

  std::cout << "print c with Collection::iterator" << std::endl;
  for(Collection::iterator it = c.begin(); it != c.end(); ++it)
    std::cout << it->r << std::endl;

  std::cout << "print c with Collection::const_iterator" << std::endl;
  for(Collection::const_iterator it = c.begin(); it != c.end(); ++it)
    std::cout << it->r << std::endl;

  return 0;
}

此程序写入预期输出:

print with Collection::iterator
1
-2
4
-8
16
print with Collection::const_iterator
1
-2
4
-8
16

我如何实施过滤器

现在我想创建一个抽象过滤器,它有一个引用或指向集合的指针,具有迭代器,并且具有通过过滤器接受值的抽象函数。对于第一步,我只编写了没有迭代器的类:

class CollectionFilter
{
  private:
    Collection& col;
  public:
    CollectionFilter(Collection& c) : col(c) {}
    virtual ~CollectionFilter() {}
    Collection& collection() { return this->col; }
    iterator begin() { /* todo */ }
    iterator end() { /* todo */ }
    const_iterator begin() const { /* todo */ }
    const_iterator end() const { /* todo */ }
    virtual bool accept(const Real& x) const = 0;
};

然后,创建一个实现特定条件的新过滤器非常容易:

class CollectionFilterPositive : public CollectionFilter
{
  public:
    CollectionFilterPositive(Collection& c) : CollectionFilter(c) {}
    virtual ~CollectionFilterPositive() {}
    virtual bool accept(const Real& x) const { return x.r >= 0.0; }
};

在过滤器中实现迭代器之前,我有一些评论/问题。

  1. 此过滤器适用于非常量Collection&,那么真正需要begin() constend() const功能吗?如果是,为什么?
  2. 我无法在const Collection&上应用过滤器,但这显然是我的目标所必需的。有什么好办法呢?我是否要将类CollectionFilter复制到具有非常相似代码的类CollectionFilterConst?此外,对于必须从两个类似类继承的用户来说,这种解决方案非常混乱。
  3. 然后,让我们去实现迭代器。对于此示例,我只编写了iterator而不是const_iterator。我把它添加到我的班级:

    class CollectionFilter
    {
      public:
        class iterator
        {
          private:
            CollectionFilter*    filter;
            Collection::iterator iter;
          public:
                      iterator(CollectionFilter* f, Collection::iterator i) : filter(f), iter(i) {}
                      iterator(const iterator& i) : filter(i.filter), iter(i.iter) {}
            iterator& operator = (const iterator& i) { this->filter = i.filter; this->iter = i.iter; return *this; }
            iterator& operator ++ ()
            {
              if(this->iter != this->filter->collection().end())
              {
                do
                {
                  ++this->iter;
                } while(this->iter != this->filter->collection().end() && !this->filter->accept(*this->iter));
              }
            }
            iterator operator ++ (int) { /* similar */ }
            Real& operator * () { return *this->iter; }
            Collection::iterator operator -> () { return this->iter; }
            bool operator == (const iterator& i) const { return this->iter == i.iter; }
            bool operator != (const iterator& i) const { return this->iter != i.iter; }
        };
      public:
        iterator begin()
        {
          Collection::iterator it = this->col.begin();
          if(!this->accept(*it)) ++it;
          return CollectionFilter::iterator(this,it);
        }
        iterator end()
        {
          Collection::iterator it = this->col.end();
          return CollectionFilter::iterator(this,it);
        }
    };
    

    这也适用于这个简单的例子

    int main()
    {
      Collection c(5);
      double k = 1.0;
      for(Collection::iterator it = c.begin(); it != c.end(); ++it)
      {
        it->r = k;
        k *= -2.0;
      }
    
      std::cout << "print c with CollectionFilterPositive::iterator" << std::endl;  
      CollectionFilterPositive fc(c);
      for(CollectionFilterPositive::iterator it = fc.begin(); it != fc.end(); ++it)
        std::cout << it->r << std::endl;
    
      return 0;
    }
    

    给出预期的输出:

    print with CollectionFilterPositive::iterator
    1
    4
    16
    

    再次提出一些问题:

    1. 这种做法我完全错了吗?
    2. 我想我必须复制CollectionFilter::iterator的代码来实现CollectionFilter::const_iterator,只做很少的修改。有没有办法避免重复此代码(写入8次,如果我计算重复的类CollectionFilterConst和反向迭代器)?
    3. 我对代码的const-correctness感到不舒服。你看到了一些问题吗?
    4. 提前致谢!

2 个答案:

答案 0 :(得分:3)

  
      
  1. 此过滤器适用于非const Collection&,那么begin() constend() const功能是否真的需要?如果是,为什么?
  2.   
  3. 我无法在const Collection&上应用过滤器,但这显然是我的目标所必需的。有什么好办法呢?我是否要使用非常相似的代码将class CollectionFilter复制到class CollectionFilterConst?此外,对于必须从两个类似类继承的用户来说,这种解决方案非常混乱。
  4.   

这些问题非常相关。基本上,将过滤限制为非常量Collection是否有意义?这对我来说并没有太大的影响。我们根本不修改CollectionFilter对象,只修改基础Collection对象(可能),Filter的功能与Collection是否无关是const。把它们放在一起,它需要一个模板:

template <typename C>
class Filter {
    static_assert(std::is_same<
                      std::decay_t<C>,
                      Collection
                  >::value, "Can only filter a Collection.");

    using collection_iterator = decltype(std::declval<C&>().begin());

    C& collection_;
public:
    Filter(C& collection) : collection_(collection) { }

    struct iterator { 
        /* TODO, use collection_iterator */
    };

    iterator begin() const { /* TODO */ };
    iterator end() const   { /* TODO */ };
};

这样,Filter<Collection>::collection_iteratorCollection::iteratorFilter<const Collection>::collection_iteratorCollection::const_iterator。你不能做Filter<std::vector<int>>

这类问题也解答了你的其他问题 - 这是一个const - 过滤任何集合的正确,非重复的方法。

为避免额外输入,您还可以创建构建器功能:

template <typename <typename> class F, typename C>
F<C> makeFilter(C& collection) {
    return F<C>(collection);
}

auto filter = makeFilter<CollectionFilterPositive>(some_collection);

const的迭代器的filter - 取决于const的{​​{1}} - 。

我也会考虑Boost.IteratorFacade来编写some_collection,这会节省你一些时间和一些麻烦。

答案 1 :(得分:1)

我建议删除CollectionFilter类,而是使用Collection::filter_iterator_tmpl模板类,其中包含两个实例Collection::filter_iteratorCollection::const_filter_iterator

Collection::filter_iterator_tmpl可以像这样实现:

class Collection {         
    template<typename Iterator, typename Predicate>
    class filter_iterator_tmpl :
    public std::iterator<std::input_iterator_tag, typename Iterator::value_type, typename Iterator::difference_type, typename Iterator::pointer, typename Iterator::reference> {
    private:
        Iterator underlying_iterator_;
        Predicate predicate_;

    public:
        filter_iterator_tmpl& operator++() {
            do {
                ++ underlying_iterator_;
            } while(! predicate_(*underlying_iterator_));
            return *this;
        }

        typename Iterator::reference operator*() const {
            return *underlying_iterator_;
        }

        ....
    }

};

可以通过让Predicate成为具有virtual bool PolymorphicPredicate::operator(Real) const函数的多态函数来添加多态性。

然后

Collection将定义实际的过滤器迭代器:

class Collection {
private:
    template<typename Iterator, typename Predicate>
    class filter_iterator_tmpl;
public:
    template<typename Predicate>
    using filter_iterator = filter_iterator_tmpl<Collection::iterator, Predicate>;

    template<typename Predicate>
    using const_filter_iterator = filter_iterator_tmpl<Collection::const_iterator, Predicate>;

    template<typename Predicate>
    filter_iterator<Predicate> begin_filter(const Predicate& pred);

    template<typename Predicate>
    const_filter_iterator<Predicate> begin_filter(const Predicate& pred) const;
}

Boost以类似的方式实现了一个通用的“Filter Iterator”:http://www.boost.org/doc/libs/1_46_1/libs/iterator/doc/filter_iterator.html 作为独立类,而不是容器类的一部分。

关于const-correctness: C ++中的容器(std::vectorstd::mapstd::string等)拥有它们的内容对象:它们创建和删除它们,并且需要确保通过const访问容器,你也只获得对内容对象的const访问权限。需要实现它们以强制执行此操作,因为它们访问其分配的存储的基础指针没有这种所有权概念。这就是为什么他们需要有两个版本的迭代器(iteratorconst_iterator)。 迭代器本身不拥有该对象:使用const访问iterator,您无法推进迭代器,但仍然可以获得对该对象的非const访问。

问题1/2: CollectionFilter是有问题的,因为它不拥有它提供访问的对象,但是对过滤器的const / non-const访问应该只给对象的const / non-const访问。 因为它包含对Collection的引用,并且它应该对Collection的const和非const访问都有效,所以需要两个版本CollectionFilterConstCollectionFilter用这种方法。

问题4: 一旦从const-correct容器对象拆分为const和非const访问的两个类,就必然会有一些代码重复。 模板避免必须手动实现两个版本。还有一些其他的复杂情况,例如将iteratorconst_iterator进行比较,并从const_iterator构建iterator,但不是相反......

问题3/5: 见上文。

相关问题