自制迭代器的const正确性

Const-correctness of self made iterators

本文关键字:const 正确性 迭代器      更新时间:2023-10-16

总体目标

我管理一个对象集合(RealCollection作为简单的例子)。然后在集合上定义迭代器。即:iterator, const_iterator, reverse_iterator, const_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. 此过滤器适用于非const Collection&,那么,begin() constend() const功能真的需要吗?如果是,为什么?
  2. 我不能在const Collection&上应用过滤器,但它显然是我的目标所必需的。有什么好办法呢?我是否需要将类CollectionFilter复制到具有非常相似代码的类CollectionFilterConst ?此外,这种解决方案对于必须从两个类似的类继承的用户来说是相当混乱的。

然后,让我们看看迭代器的实现。对于本例,我只编写了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

还有一些问题:

我完全错了吗?
  • 我想我必须复制CollectionFilter::iterator的代码来实现CollectionFilter::const_iterator,只有很小的修改。是否有一种方法来避免重复这段代码(写了8次,如果我计算重复的类CollectionFilterConst和反向迭代器)?
  • 我对我的代码的常量正确性感到不舒服。你看到问题了吗?
  • 提前感谢!

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

    这些问题是非常相关的。基本上,将您的过滤限制为非const 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_iterator就是Collection::iterator, Filter<const Collection>::collection_iterator就是Collection::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);
    

    filter迭代器的const度依赖于some_collectionconst度。

    我也会看看Boost。用于编写Filter::iterator的IteratorFacade,它将为您节省一些时间和一些头痛。

    我建议放弃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以类似的方式实现了一个通用的"过滤器迭代器":http://www.boost.org/doc/libs/1_46_1/libs/iterator/doc/filter_iterator.html作为一个独立的类,而不是作为容器类的一部分。

    关于const-correctness

    :c++中的容器(std::vector, std::map, std::string等)拥有它们的内容对象:它们创建和删除它们,并且需要确保通过对容器的const访问,您也只能对内容对象进行const访问。它们需要被实现来强制执行这一点,因为它们访问所分配存储的底层指针没有所有权的概念。这就是为什么它们需要两个版本的迭代器(iteratorconst_iterator)。迭代器本身不拥有对象:使用const访问iterator时,不能推进迭代器,但仍然可以对对象进行非const访问。

    1/2的问题:CollectionFilter是有问题的,因为它不拥有它提供访问的对象,但是对过滤器的const/非const访问应该只给予对对象的const/非const访问。因为它保存了对Collection的引用,并且它应该同时用于对Collection的const和非const访问,所以使用这种方法需要CollectionFilterConstCollectionFilter两个版本。

    问题4:一旦将const正确的容器对象拆分为const和非const访问的两个类,就必然存在一些代码重复。模板避免了必须手动实现这两个版本。还有一些额外的复杂性,例如比较iteratorconst_iterator,以及从iterator构造const_iterator,但不能反过来…

    3/5

    问题:看到上面。