11

一般的な目標

オブジェクトのコレクションを管理します(簡単CollectionReal例として)。次に、コレクションに反復子を定義しました。つまりiterator、 、const_iteratorreverse_iteratorおよびconst_reverse_iterator. iteratorこの例では、とだけに注目しconst_iteratorます。他の 2 つは非常によく似ています。

その後、特定の条件に関して要素を保持するかどうかを指定するコレクションのフィルターを定義したいと思います。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. このフィルターは非 constCollection&で機能しますが、begin() constandend() const関数は本当に必要ですか? はいの場合、なぜですか?
  2. にフィルターを適用することはできませんが、const Collection&私の目標には明らかに必要です。それを行う良い方法は何ですか?CollectionFilterクラスCollectionFilterConstを非常によく似たコードを持つクラスに複製する必要がありますか? さらに、このソリューションは、2 つの類似したクラスから継承する必要があるユーザーにとって非常に混乱します。

それでは、イテレータの実装に行きましょう。この例では、 のみを記述し、 は記述し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このコードの重複を避ける方法はありますか (重複したクラスCollectionFilterConstと逆の反復子を数えると、8 回書かれています)。
  3. コードの const の正確さに満足できません。いくつか問題がありますか?

前もって感謝します !

4

2 に答える 2

3
  1. このフィルタは非const Collection&で機能します。では、関数begin() constend() const関数は本当に必要ですか? はいの場合、なぜですか?
  2. にフィルターを適用することはできませんが、const Collection&私の目標には明らかに必要です。それを行う良い方法は何ですか?非常によく似たコードでを複製するclass CollectionFilter必要がありますか? class CollectionFilterConstさらに、このソリューションは、2 つの類似したクラスから継承する必要があるユーザーにとって非常に混乱します。

これらの質問は非常に関連しています。基本的に、フィルタリングを非 const に制限することは理にかなっていますCollectionか? それは私にはあまり意味がありません。CollectionFilterオブジェクトをまったく変更せず、基礎となるCollectionオブジェクトのみを (潜在的に) 変更し、 の機能はが であるFilterかどうかに依存しません。それをすべてまとめると、テンプレートが必要になります。Collectionconst

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::iteratorFilter<const Collection>::collection_iteratorCollection::const_iteratorFilter<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);

constwillのイテレータの -ness は、の-ness にfilter依存します。constsome_collection

また、 Boost.IteratorFacadeを書いて調べることもFilter::iteratorできます。時間と頭痛の種を節約できます。

于 2015-06-10T19:07:13.467 に答える
1

クラスを削除しCollectionFilter、代わりにCollection::filter_iterator_tmpl2 つのインスタンス化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 アクセスを使用して、コンテンツ オブジェクトへの const アクセスのみを取得することを確認する必要があります。割り当てられたストレージにアクセスするための基になるポインターには、この所有権の概念がないため、これを強制するように実装する必要があります。iteratorこれが、2 つのバージョンの反復子 (および)が必要な理由const_iteratorです。イテレータ自体はオブジェクトを所有していません。 への const アクセスではiterator、イテレータを進めることはできませんが、オブジェクトへの非 const アクセスは取得できます。

質問 1/2: CollectionFilterアクセスを提供するオブジェクトを所有していないため問題がありますが、フィルターへの const/non-const アクセスは、オブジェクトへの const/non-const アクセスのみを許可する必要があります。への参照を保持し、 へCollectionの const アクセスと非 const アクセスの両方で機能する必要があるためCollection、2 つのバージョンがCollectionFilterありConstCollectionFilter、そのアプローチで必要になります。

質問 4: const 正しいコンテナー オブジェクトから、const アクセスと non-const アクセスの 2 つのクラスに分割すると、必然的にコードの重複が発生します。テンプレートを使用すると、両方のバージョンを手動で実装する必要がなくなります。また、 と を比較しiteratorたり、からconst_iteratorを構築したりconst_iteratorしますiteratorが、その逆はありません...

質問 3/5: 上記を参照してください。

于 2015-06-10T19:31:45.700 に答える