Возврат «любого типа итератора ввода» вместо vector :: iterator или list :: iterator

Предположим, я хочу реализовать на C ++ структуру данных для хранения ориентированных графов. Дуги будут храниться в узлах благодаря контейнерам STL. Я бы хотел, чтобы пользователи могли выполнять итерацию по дугам узла способом, подобным STL.

У меня проблема в том, что я не хочу раскрывать в классе Node (который на самом деле будет абстрактным базовым классом), какой контейнер STL я буду использовать в конкретном классе. Поэтому я не хочу, чтобы мои методы возвращали std :: list :: iterator или std :: vector :: iterator ...

Я пробовал это:

class Arc;

typedef std::iterator<std::random_access_iterator_tag, Arc*> ArcIterator;  // Wrong!

class Node {
public:
  ArcIterator incomingArcsBegin() const {
    return _incomingArcs.begin();
  }
private:
  std::vector<Arc*> _incomingArcs;
};

Но это неверно, потому что vector :: const_iterator нельзя использовать для создания ArcIterator. Так что же может быть этим ArcIterator?

Я нашел этот документ о пользовательских итераторах для STL, но это не помогло. Я, должно быть, сегодня полноват ...;)


person Xavier Nodet    schedule 24.09.2008    source источник
comment
Я был очень доволен boost:graph. Если вы действительно пишете адаптеры для графовых структур, подумайте об их использовании.   -  person Alexandre C.    schedule 03.02.2011


Ответы (10)


Попробуй это:

class Arc;
class Node {
private:
  std::vector<Arc*> incoming_;
public:
  typedef std::vector<Arc*>::iterator iterator;
  iterator incoming_arcs_begin()
  { return incoming_.begin(); }
};

И используйте Node :: iterator в остальной части кода. Когда / если вы меняете контейнер, вы должны изменить typedef в одном месте. (Вы можете сделать еще один шаг вперед с дополнительным typedef для хранилища, в данном случае вектором.)

Что касается проблемы с константой, либо определите const_iterator вектора как свой итератор, либо определите типы двойных итераторов (константная и неконстантная версия), как это делает вектор.

person zvrba    schedule 24.09.2008

Взгляните на any_iterator Adobe: этот класс использует технику под названием стирание типа , при помощи которого тип итератора underyling скрывается за абстрактным интерфейсом. Осторожно: использование any_iterator влечет за собой штраф во время выполнения из-за виртуальной диспетчеризации.

person Community    schedule 24.09.2008

Я хочу думать, что должен быть способ сделать это через прямой STL, аналогичный тому, что вы пытаетесь сделать.

Если нет, вы можете изучить использование фасады и адаптеры итераторов boost, где вы можете определять свои собственные итераторы или адаптировать другие объекты в итераторы.

person Doug T.    schedule 24.09.2008

Чтобы скрыть тот факт, что ваши итераторы основаны на std::vector<Arc*>::iterator, вам нужен класс итератора, который делегирует std::vector<Arc*>::iterator. std::iterator этого не делает.

Если вы посмотрите на файлы заголовков в стандартной библиотеке C ++ вашего компилятора, вы можете обнаружить, что std::iterator не очень полезен сам по себе, если вам не нужен только класс, который определяет typedefs для iterator_category, value_type и т. Д.

Как сказал Дуг Т. в своем ответе, в библиотеке boost есть классы, которые упрощают написание итераторов. В частности, boost::indirect_iterator может быть полезным, если вы хотите, чтобы ваши итераторы возвращали Arc при разыменовании вместо Arc*.

person bk1e    schedule 24.09.2008

Рассмотрите возможность использования шаблона посетителя и инвертирования отношения: вместо того, чтобы запрашивать структуру графа для контейнера data, вы даете графу функтор и позволяете графу применять этот функтор к своим данным.

Шаблон посетителя - это часто используемый шаблон на графиках, ознакомьтесь с документацией библиотеки графов boost по концепциям посетителей.

person user150753    schedule 05.08.2009

Если вы действительно не хотите, чтобы клиент этого класса знал, что он использует вектор внизу, но все же хотите, чтобы он мог каким-то образом перебирать его, вам, скорее всего, потребуется создать класс, который перенаправляет все его методы в std :: vector :: iterator.

Альтернативой может быть шаблонизация узла на основе типа контейнера, который он должен использовать внизу. Тогда клиенты точно знают, какой тип контейнера он использует, потому что они сказали им использовать его.

Лично я не думаю, что обычно имеет смысл инкапсулировать вектор вдали от пользователя, но все же предоставлять большую часть (или даже часть) его интерфейса. Слишком тонкий слой инкапсуляции, чтобы действительно приносить пользу.

person Greg Rogers    schedule 24.09.2008

Я посмотрел в заголовочном файле VECTOR.

vector<Arc*>::const_iterator

это typedef для

allocator<Arc*>::const_pointer

Может быть, это ваш ArcIterator? Нравится:

typedef allocator<Arc*>::const_pointer ArcIterator;
person TonJ    schedule 24.09.2008

Вы можете создать шаблон для класса Node и ввести в нем как итератор, так и const_iterator.

Например:

class Arc {};

template<
  template<class T, class U> class Container = std::vector,
  class Allocator = std::allocator<Arc*>
>
class Node
{
  public:
    typedef typename Container<Arc*, Allocator>::iterator ArcIterator;
    typedef typename Container<Arc*, Allocator>::Const_iterator constArcIterator;

    constArcIterator incomingArcsBegin() const {
      return _incomingArcs.begin();
    }

    ArcIterator incomingArcsBegin() {
      return _incomingArcs.begin();
    }
  private:
    Container<Arc*, Allocator> _incomingArcs;
};

Я не пробовал этот код, но он дает вам представление. Однако вы должны заметить, что использование ConstArcIterator просто запретит модификацию указателя на дугу, а не модификацию самой дуги (например, с помощью неконстантных методов).

person Luc Touraille    schedule 24.09.2008

C ++ 0x позволяет сделать это с помощью автоматического определения типа.

В новом стандарте этот
for (vector::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr
может быть заменен этим
for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)

Точно так же вы сможете вернуть любой подходящий итератор и сохранить его в переменной auto.

Пока не вступит в силу новый стандарт, вам придется либо шаблонизировать свой класс, либо предоставлять абстрактный интерфейс для доступа к элементам вашего списка / вектора. Например, вы можете сделать это, сохранив итератор в переменной-члене и предоставив функции-члены, такие как begin() и next(). Это, конечно, означало бы, что только один цикл за раз может безопасно перебирать ваши элементы.

person Dima    schedule 24.09.2008
comment
Я не думаю, что это поможет мне определить мой интерфейс, не ограничивая выбор контейнера разработчиками. - person Xavier Nodet; 24.09.2008
comment
Истинный. auto, как и шаблоны, использовал вывод типа во время компиляции. Слишком поздно. Реализация может быть скомпилирована после кода с использованием интерфейса. - person MSalters; 24.09.2008

Ну, поскольку std::vector гарантированно имеет непрерывное хранилище, это должно быть идеально для этого:

class Arc;
typedef Arc* ArcIterator;

class Node {
public:
    ArcIterator incomingArcsBegin() const {
        return &_incomingArcs[0]
    }

    ArcIterator incomingArcsEnd() const {
        return &_incomingArcs[_incomingArcs.size()]
    }
private:
    std::vector<Arc*> _incomingArcs;
};

По сути, указатели функционируют так же, как итераторы с произвольным доступом, поэтому они могут быть достаточной заменой.

person Evan Teran    schedule 25.09.2008