【问题标题】:Const-correctness of self made iterators自制迭代器的 const 正确性
【发布时间】:2015-08-23 01:13:35
【问题描述】:

总体目标

我管理一个对象集合(RealCollection 作为简单示例)。然后我在我的集​​合上定义了迭代器。这意味着: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&amp;,那么,begin() constend() const 函数真的需要吗?如果是,为什么?
  2. 我无法在const Collection&amp; 上应用过滤器,但它显然是我的目标所必需的。有什么好方法可以做到这一点?我是否必须使用非常相似的代码将 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

再次,一些问题:

  1. 我对这种方法完全错误吗?
  2. 我想我必须复制CollectionFilter::iterator 的代码来实现CollectionFilter::const_iterator,只需稍作修改。有没有办法避免重复这段代码(写了 8 次,如果我计算重复的类 CollectionFilterConst 和反向迭代器)?
  3. 我对我的代码的 const 正确性感到不舒服。您是否发现一些问题?

提前致谢!

【问题讨论】:

    标签: c++ iterator const-iterator


    【解决方案1】:
    1. 此过滤器适用于非const Collection&amp;,那么,begin() constend() const 函数真的需要吗?如果是,为什么?
    2. 我无法在const Collection&amp; 上应用过滤器,但它显然是我的目标所必需的。有什么好方法可以做到这一点?我是否必须使用非常相似的代码将class CollectionFilter 复制到class CollectionFilterConst?此外,对于必须从两个相似类继承的用户来说,这种解决方案相当混乱。

    这些问题非常相关。基本上,将过滤限制为非常量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&lt;Collection&gt;::collection_iteratorCollection::iteratorFilter&lt;const Collection&gt;::collection_iteratorCollection::const_iterator。而你不能这样做Filter&lt;std::vector&lt;int&gt;&gt;

    这种方法也可以回答您的其余问题 - 这是一种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-ness 将取决于 some_collectionconst-ness。

    我还会查看 Boost.IteratorFacade 以编写 Filter::iterator,它会为您节省一些时间和一些麻烦。

    【讨论】:

      【解决方案2】:

      我建议删除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 正确性: C++ 中的容器(std::vectorstd::mapstd::string 等)拥有它们的内容对象:它们创建和删除它们,并且需要确保通过对容器的 const 访问,您也只能获得对内容对象。它们需要被实现以强制执行这一点,因为它们访问分配的存储所使用的底层指针没有这种所有权概念。这就是为什么他们需要有两个版本的迭代器(iteratorconst_iterator)。 迭代器本身并不拥有该对象:通过对 iterator 的 const 访问,您无法推进迭代器,但您仍然可以获得对对象的非 const 访问。

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

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

      问题 3/5: 见上文。

      【讨论】:

      • 相当有趣的方法。我会详细检查的。
      • 其实boost::filter_iterator 解决了我的问题。 :-) 了解下面的内容非常有趣。谢谢你的好回答。
      猜你喜欢
      • 2021-02-13
      • 2022-08-09
      • 1970-01-01
      • 2016-04-09
      • 2013-11-25
      • 2020-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多