Tworzenie własnych Iteratorów


141

Próbuję nauczyć się C ++, więc wybacz mi, jeśli to pytanie pokazuje brak podstawowej wiedzy, widzisz, faktem jest, że mam brak podstawowej wiedzy.

Potrzebuję pomocy przy tworzeniu iteratora dla klasy, którą stworzyłem.

Mam klasę „Kształt”, która zawiera zbiór punktów. Mam klasę „Piece”, która odwołuje się do Shape i definiuje pozycję Shape. Kawałek nie ma kształtu, tylko odnosi się do kształtu.

Chcę, żeby wyglądało na to, że Kawałek jest zbiornikiem Punktów, które są takie same jak te z Kształt, do którego się odnosi, ale z dodanym przesunięciem pozycji Kawałka.

Chcę mieć możliwość iteracji punktów elementu tak, jakby Element sam w sobie był pojemnikiem. Poczytałem trochę i nie znalazłem niczego, co mi pomogło. Byłbym bardzo wdzięczny za wszelkie wskazówki.


6
Opublikowanie przykładowego kodu pomogłoby w opisaniu tego, co robisz, lepiej niż zwykły angielski tekst.
Greg Rogers,

3
Tworzenie niestandardowych iteratorów prawdopodobnie nie jest podstawowym szczytem, ​​przynajmniej średnio zaawansowanym.
ldog

Odpowiedzi:


41

Powinieneś użyć Boost.Iterators. Zawiera szereg szablonów i koncepcji do implementacji nowych iteratorów i adapterów dla istniejących iteratorów. Napisałem artykuł na ten temat ; jest w magazynie ACCU z grudnia 2008 roku. Omawia eleganckie rozwiązanie (IMO) dla dokładnie twojego problemu: ujawnianie kolekcji elementów z obiektu przy użyciu Boost.Iterators.

Jeśli chcesz używać tylko stl, w książce Josuttisa znajduje się rozdział poświęcony implementowaniu własnych iteratorów STL.


3
Tylko drobna uwaga: książka mówi o bibliotece standardowej C ++, a nie o STL - te są różne, ale często się
gubię

62

/ EDYCJA: Rozumiem, własny iterator jest tutaj właściwie potrzebny (najpierw źle odczytałem pytanie). Mimo wszystko pozostawiam poniższy kod, ponieważ może być przydatny w podobnych okolicznościach.


Czy rzeczywiście potrzebny jest tutaj własny iterator? Być może wystarczy przesłać wszystkie wymagane definicje do kontenera zawierającego rzeczywiste Punkty:

// Your class `Piece`
class Piece {
private:
    Shape m_shape;

public:

    typedef std::vector<Point>::iterator iterator;
    typedef std::vector<Point>::const_iterator const_iterator;

    iterator begin() { return m_shape.container.begin(); }

    const_iterator begin() const { return m_shape.container.begin(); }

    iterator end() { return m_shape.container.end(); }

    const_iterator end() const { return m_shape.const_container.end(); }
}

Zakłada się, że używasz vectorwewnętrznie, ale typ można łatwo dostosować.


być może chce użyć algorytmu STL lub funkcji funkcjonalnych przeciwko swojej klasie ...
gbjbaanb

2
Pierwotne pytanie faktycznie mówi, że iterator kontenera kawałkowego powinien modyfikować wartości podczas ich zwracania. Wymagałoby to osobnego iteratora, chociaż prawdopodobnie powinien być dziedziczony lub w inny sposób uzyskiwany głównie z oryginału.
workmad3

@gbjbaanb: Zaletą mojego kodu jest to, że może być używany przez algorytmy STL.
Konrad Rudolph

1
Kilka lat później i nadal jest to jeden z najlepszych wyników w Google ... Teraz można to uogólnić, robiąc coś takiego:auto begin() -> decltype(m_shape.container.begin()) { return m_shape.container.begin(); }
user2962533



2

Pisanie niestandardowych iteratorów w C ++ może być dość rozwlekłe i trudne do zrozumienia.

Ponieważ nie mogłem znaleźć minimalnego sposobu na napisanie niestandardowego iteratora, napisałem ten nagłówek szablonu, który może pomóc. Na przykład, aby uczynić Pieceklasę iterowalną:

#include <iostream>
#include <vector>

#include "iterator_tpl.h"

struct Point {
  int x;
  int y;
  Point() {}
  Point(int x, int y) : x(x), y(y) {}
  Point operator+(Point other) const {
    other.x += x;
    other.y += y;
    return other;
  }
};

struct Shape {
  std::vector<Point> vec;
};

struct Piece {
  Shape& shape;
  Point offset;
  Piece(Shape& shape, int x, int y) : shape(shape), offset(x,y) {}

  struct it_state {
    int pos;
    inline void next(const Piece* ref) { ++pos; }
    inline void begin(const Piece* ref) { pos = 0; }
    inline void end(const Piece* ref) { pos = ref->shape.vec.size(); }
    inline Point get(Piece* ref) { return ref->offset + ref->shape.vec[pos]; }
    inline bool cmp(const it_state& s) const { return pos != s.pos; }
  };
  SETUP_ITERATORS(Piece, Point, it_state);
};

Wtedy byłbyś w stanie używać go jako zwykłego kontenera STL:

int main() {
  Shape shape;
  shape.vec.emplace_back(1,2);
  shape.vec.emplace_back(2,3);
  shape.vec.emplace_back(3,4);

  Piece piece(shape, 1, 1);

  for (Point p : piece) {
    std::cout << p.x << " " << p.y << std::endl;
    // Output:
    // 2 3
    // 3 4
    // 4 5
  }

  return 0;
}

Pozwala również na dodawanie innych typów iteratorów, takich jak const_iteratorlub reverse_const_iterator.

Mam nadzieję, że to pomoże.


1

Rozwiązaniem Twojego problemu nie jest tworzenie własnych iteratorów, ale wykorzystanie istniejących kontenerów i iteratorów STL. Przechowuj punkty w każdym kształcie w pojemniku, takim jak wektor.

class Shape {
    private:
    vector <Point> points;

Od tego momentu zależy od Twojego projektu. Najlepszym podejściem jest iteracja punktów w metodach wewnątrz Shape.

for (vector <Point>::iterator i = points.begin(); i != points.end(); ++i)
    /* ... */

Jeśli potrzebujesz uzyskać dostęp do punktów poza Shape (może to być oznaką wadliwego projektu), możesz utworzyć w Shape metody, które zwrócą funkcje dostępu iteratora dla punktów (w takim przypadku również utwórz publiczny krój pisma dla kontenera punktów). Spójrz na odpowiedź Konrada Rudolpha, aby poznać szczegóły tego podejścia.


3
Nadal będzie musiał stworzyć swój własny iterator, który przekazuje żądania do Elementu do kształtów znajdujących się w tym kawałku. Własne iteratory są tutaj świetnym narzędziem i bardzo eleganckie w użyciu.
Roel
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.