Как создать operator-> в итераторе без контейнера? - PullRequest
0 голосов
/ 27 октября 2009
template <class Enum>
class EnumIterator {
 public:

  const Enum* operator-> () const {
    return &(Enum::OfInt(i));  // warning: taking address of temporary
  }

  const Enum operator* () const {
    return Enum::OfInt(i);     // There is no problem with this one!
  }

 private:
  int i;
};

Я получаю это предупреждение выше. В настоящее время я использую этот хак:

template <class Enum>
class EnumIterator {
 public:
  const Enum* operator-> () {
    tmp = Enum::OfInt(i);
    return &tmp;
  }
 private:
  int i;
  Enum tmp;
};

Но это ужасно, потому что итератор служит отсутствующим контейнером.

Как правильно перебирать диапазон значений?

Обновление: Итератор специализируется на конкретном наборе объектов, которые поддерживают именованный статический конструктор OfInt (обновлен фрагмент кода).

Пожалуйста, не придирайтесь к коду, который я вставил, а просто попросите разъяснений. Я пытался извлечь простой кусок.

Если вы хотите знать, что T будет сильным типом enum (по сути, int, упакованный в класс). Будет typedef EnumIterator Iterator; внутри класса EnumX.

Обновление 2: добавлены константы, чтобы указать, что члены класса строгого перечисления, к которым будет осуществляться доступ через ->, не изменяют возвращенное временное перечисление.

Обновлен код с оператором *, который не вызывает проблем.

Ответы [ 5 ]

1 голос
/ 27 октября 2009
Enum* operator-> () {
  tmp = Enum::OfInt(i);
  return &tmp;
}

Проблема не в том, что это некрасиво, а в том, что это небезопасно. Что происходит, например, в коде, подобном следующему:

void f(EnumIterator it)
{
   g(*it, *it);
}

Теперь g() заканчивается двумя указателями, каждый из которых указывает на один и тот же внутренний временный объект, который должен был быть деталью реализации вашего итератора. Если g() пишет через один указатель, другое значение также изменяется. Уч.

Ваша проблема в том, что эта функция должна возвращать указатель, но у вас нет объекта, на который можно указать. Неважно, что, вам придется это исправить.

Я вижу две возможности:

  1. Поскольку эта вещь, похоже, заключает в себе enum, а типы перечисления не имеют членов, этот operator-> в любом случае бесполезен (он не будет создан, пока не будет вызван, и не может быть вызван, поскольку это приведет к компиляции ошибка) и может быть безопасно опущено.
  2. Сохраните объект типа вправо (что-то вроде Enum::enum_type) внутри итератора и приведите его к / из int, только если вы хотите выполнять операции, подобные целочисленным (например, приращение ) в теме.
1 голос
/ 27 октября 2009

Существует много разновидностей итераторов.

Например, в векторе итераторы обычно представляют собой простые указатели:

template <class T>
class Iterator
{
public:
  T* operator->() { return m_pointer; }

private:
  T* m_pointer;
};

Но это работает, потому что на самом деле вектор - это просто массив.

В двусвязном списке он будет другим, список будет состоять из узлов.

template <class T>
struct Node
{
  Node* m_prev;
  Node* m_next;
  T m_value;
};

template <class T>
class Iterator
{
public:
  T* operator->() { return m_node->m_value; }

private:
  Node<T>* m_node;
};

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

Возможно, вы захотите добавить дополнительные возможности отладки:

  • возможность аннулировать итератор
  • возможность проверки диапазона
  • проверка контейнера (т. Е. Проверка при сравнении двух итераторов, что они ссылаются на один и тот же контейнер для начала)

Но это тонкости, и для начала, это немного сложнее.

Обратите внимание также Boost.Iterator , который помогает с кодом пластины котла.

РЕДАКТИРОВАТЬ: (сгруппированы обновления 1 и 2)

В вашем случае хорошо, если ваш итератор - просто int, вам больше не нужно. На самом деле для сильного перечисления вам даже не нужен итератор, вам просто нужны оператор ++ и оператор--:)

Смысл ссылки на контейнер обычно заключается в реализации этих операторов ++ и -. Но из вашего элемента достаточно иметь значение int (при условии, что оно достаточно большое) и способ добраться до предыдущего и следующего значений.

Было бы проще, если бы у вас был статический вектор, вы могли бы просто повторно использовать векторный итератор.

0 голосов
/ 27 октября 2009

Поскольку нет контейнера, я остановился на слиянии итератора в свой сильный Enum. Я запускаю raw int в -1 для поддержки пустых перечислений (limit == 0) и могу использовать цикл for с TryInc.

Вот код:

template <uint limit>
class Enum {
 public:
  static const uint kLimit = limit;

  Enum () : raw (-1) {
  }

  bool TryInc () {
    if (raw+1 < kLimit) {
      raw += 1;
      return true;
    }
    return false;
  }

  uint GetRaw() const {
    return raw;
  }

  void SetRaw (uint raw) {
    this->raw = raw;
  }

  static Enum OfRaw (uint raw) {
    return Enum (raw);
  }

  bool operator == (const Enum& other) const {
    return this->raw == other.raw;
  }

  bool operator != (const Enum& other) const {
    return this->raw != other.raw;
  }

 protected:
  explicit Enum (uint raw) : raw (raw) {
  }

 private:
  uint raw;
};

Использование:

class Color : public Enum <10> {
 public:
  static const Color red;

  // constructors should be automatically forwarded ...
  Color () : Enum<10> () {
  }

 private:

  Color (uint raw) : Enum<10> (raw) {
  }
};

const Color Color::red = Color(0);


int main() {
  Color red = Color::red;

  for (Color c; c.TryInc();) {
    std::cout << c.GetRaw() << std::endl;
  }
}
0 голосов
/ 27 октября 2009

Что возвращает OfInt? Похоже, что в этом случае возвращается неправильный тип. Он должен возвращать T *, вместо этого он, кажется, возвращает T по значению, по которому вы затем берете адрес. Это может привести к некорректному поведению, так как потеряет все обновления, сделанные через ->.

0 голосов
/ 27 октября 2009

Итератор выполняет итерацию для определенного контейнера. Реализация зависит от того, что это за контейнер. Указатель, который вы возвращаете, должен указывать на член этого контейнера. Вам не нужно копировать его, но вам нужно отслеживать, в каком контейнере вы выполняете итерацию, и где вы находитесь (например, индекс для вектора), предположительно инициализированный в конструкторе итератора. Или просто используйте STL.

...