web-dev-qa-db-ja.com

クラスに標準イテレータを実装する方法

私は通常、基になるフィールドとして標準のコンテナーを使用しているクラスがあります。たとえば、私はクラスを持っています

template <typename T>
class Vec_3D
{
public:
    /* ... */
    std::array<T, 3> vec;
    /* ... */
};

これには変数vecが1つだけあり、残りはベクトルを扱うときに必要な関数です。次のような範囲ベースのforループを使用できるようにしたい

Vec_3D<double> vec;
for (double val : vec) {/*...*/}

これはstd::array<double, 3>を明らかに繰り返します。

_std::array<T, 3>のイテレータを順に呼び出すクラスにイテレータを実装する方法

私は この質問 から始めて、クラスの反復子を次のように定義しようとしました

typedef std::iterator<std::random_access_iterator_tag, T, ptrdiff_t, T*, T&> iterator;
typedef std::iterator<std::random_access_iterator_tag, const T, ptrdiff_t, const T*, const T&> const_iterator;

inline iterator begin() noexcept { return vec.begin(); }
inline const_iterator cbegin() const noexcept { return vec.cbegin(); }
inline iterator end() noexcept { return vec.end(); }
inline const_iterator cend() const noexcept { return vec.end(); }

しかし、コンパイルエラーが発生しました

error: no match for ‘operator!=’ (operand types are ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’ and ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’)

およびoperator++, operator*

7
Michal

範囲ベースのforループでは、クラスにbegin()およびend()メソッド(またはstd::begin()std::end()のオーバーロード)が必要です。イテレータ。それらのイテレータがどこから来たかは関係ありません。したがって、最も簡単な解決策は、独自のイテレータを定義するのではなく、配列自体のイテレータを使用することです。例:

template <typename T>
class Vec_3D
{
public:
    typedef typename std::array<T, 3> array_type;
    typedef typename array_type::iterator iterator;
    typedef typename array_type::const_iterator const_iterator;
    // or:
    // using array_type = std::array<T, 3>;
    // using iterator = array_type::iterator;
    // using const_iterator = array_type::const_iterator;
    ...

    inline iterator begin() noexcept { return vec.begin(); }
    inline const_iterator cbegin() const noexcept { return vec.cbegin(); }
    inline iterator end() noexcept { return vec.end(); }
    inline const_iterator cend() const noexcept { return vec.cend(); }
    ...

private:
    array_type vec;
};
10
Remy Lebeau

std::iteratorは、典型的なイテレータが必要とするtypedefsを定義するためのヘルパータイプです。クラス内のこれらのtypedefは、std::iterator_traitsをイテレータと連携させます。

ただし、実際に必要な操作が実装されるわけではありません。

Std委員会は、標準のイテレータがそれらのtypedefを持つ必要があることを指定することを好まなかったため、非推奨になりました。typedefの記述は、std::iteratorテンプレートに渡す引数を理解することほど大したことではありませんでした。

ここで簡単にできるのは、基盤となるコンテナのイテレータを盗むだけです。これにより、抽象化リークが発生しますが、効率的で簡単です。

template <typename T>
struct Vec_3D {
  using container=std::array<T, 3>;
  using iterator=typename container::iterator;
  using const_iterator=typename container::const_iterator;

  iterator begin() { return vec.begin(); }
  iterator end() { return vec.end(); }
  const_iterator begin() const { return vec.begin(); }
  const_iterator end() const { return vec.end(); }
private:
  /* ... */
  container vec;
  /* ... */
};

基礎となるコンテナータイプを公開したくない場合は、基礎となるコンテナーが連続したバッファーであることを保証してもかまいません。

template <typename T>
struct Vec_3D {
  using iterator=T*;
  using const_iterator=T const*;

  iterator begin() { return vec.data(); }
  iterator end() { return vec.data()+vec.size(); }
  const_iterator begin() const { return vec.data(); }
  const_iterator end() const { return vec.data()+vec.size(); }
private:
  /* ... */
  std::array<T,3> vec;
  /* ... */
};

ポインタは有効なイテレータなので。

この「私は変更されたコンテナです」というボイラープレートを書きすぎている場合は、自動化できます。

template<class Container>
struct container_wrapper {
  using container=Container;

  using iterator=typename container::iterator;
  using const_iterator=typename container::const_iterator;

  iterator begin() { return m_data.begin(); }
  iterator end() { return m_data.end(); }
  const_iterator begin() const { return m_data.begin(); }
  const_iterator end() const { return m_data.end(); }
protected:
  Container m_data;
};

その後

template <typename T>
class Vec_3D:private container_wrapper<std::array<T,3>> {
  // ...
};

しかし、それでも少し多すぎるかもしれません。

template <typename T>
class Vec_3D:public std::array<T,3> {
  // ...
};

Baseへのポインタを介してVec_3Dを削除することは未定義の動作ですが、標準のコンテナへのポインタを削除するのは誰ですか?

これがあなたを心配するなら:

template <typename T>
class Vec_3D: private std::array<T,3> {
  using container = std::array<T,3>;
  using container::begin();
  using container::end();
  // ...
};

非公開で継承し、特定の操作をスコープに戻すことができます。

std :: iteratorは基本クラスのみであり、基本的にはいくつかの特性のコンテナーですが、独自のイテレータークラスを実装するために使用する場合は、そこから派生する必要があります。

ただし、使用する必要はありません。廃止するという提案があり、作成したイテレータで直接これらの特性を定義できます。次の質問には、提案に関する情報と、イテレータクラスの実装に役立つ情報が含まれています。- std :: iterator Beprecatedの準備

現時点では、コンテナのイテレータ型を、そのベースを使用して定義しています。実際に反復を実行できるクラスではないため、失敗します。

配列をパブリックメンバーとして公開します。 vec_3dが配列を使用して実装されていることを喜んで公開している場合(メンバー配列をパブリックに公開し続けているかどうかに関係なく)、配列のイテレータを使用するだけでかまいません。コンテナがいくつかの機能を追加したからです。

4
ROX