2014-01-28 2 views
0

У меня есть класс, который производит значения, с интерфейсом, как это:Правильного способ реализации итератора абстрактного производителя со ссылкой семантикой

template<class T> 
class Generator 
{ 
public: 
    void advance(); 
    T* get(); 
    bool done(); 
    //... 
}; 

advance функция продвигает производитель и помещает указатель на новый значение во внутреннем хранилище. Функция get возвращает этот указатель, или nullptr, если Generator сделан (хотя Generator может производить nullptrs в процессе нормальной работы, также). done возвращает значение true, если значение Producer сделано с получением значений. get возвращает указатель, позволяющий клиенту взаимодействовать с полученным значением, потенциально передавая информацию обратно на Generator.

Казалось бы, было бы очень просто реализовать итератор. Класс Generator не копируется, поэтому сам он не может быть итератором, поэтому я создал простую оболочку с необходимыми операциями. Проблема, с которой я столкнулся, - это оператор post-increment; продвижение Generator аннулирует старый указатель. Я играл с разными идеями, но ни один из них не позволяет мне удовлетворить все требования к входному итератору или выходному итератору. Я не хочу, чтобы мои итераторы имели копию значения, потому что я хотел бы иметь ссылочную семантику через указатель. Самое близкое, что я пришел, это иметь итераторы для хранения счетчика приращений и только продвигать Generator на операцию разыменования или сравнения. Это inloes const_cast и, кроме того, очень опасно. Есть ли способ реализовать правильный итератор для этого интерфейса?

+0

Правильно ли я прав: наличие действительного итератора i; другой итератор j = i; и ++ j аннулирует i!? –

+0

Во всяком случае, 'i == j' не может (не может) подразумевать' ++ i == ++ j', поэтому он ограничен вводами итераторов ввода. –

+0

Недействительно это для разыменования, да. Это нормальное поведение, допускаемое концепцией [InputIterator] (http://en.cppreference.com/w/cpp/concept/InputIterator).Проблема в том, что, хотя старые устаревшие старые итераторы недействительны, * it ++ все еще должен работать. – Lucretiel

ответ

1

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

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

EDIT:

Что касается Вашего комментария о необходимости *r++ работы: Я думаю, что следующее будет законным:

template <typename T> 
class GeneratorIterator 
{ 
    class PostIncrProxy 
    { 
     GeneratorIterator* myOwner; 
    public: 
     PostIncrProxy(GeneratorIterator* owner) 
      : myOwner(owner) 
     { 
     } 
     ~PostIncrProxy() 
     { 
      ++(*myOwner); 
     } 
     T* operator*() const 
     { 
      return **myOwner; 
     } 
    }; 

    Generator<T>* myOwner; 
public: 
    GeneratorIterator(Generator<T>& owner) 
     : myOwner(&owner) 
    { 
    } 
    GeneratorIterator()   // End iterator... 
     : myOwner(nullptr) 
    { 
    } 

    bool operator==(GeneratorIterator const& other) const 
    { 
     return (myOwner == nullptr) == (other.myOwner == nullptr); 
    } 
    bool operator!=(GeneratorIterator const& other) const 
    { 
     return !operator==(other); 
    } 

    T* operator*() const 
    { 
     assert(myOwner != nullptr); 
     return myOwner->get(); 
    } 
    // -> not necessary if we're iterating over T*. If 

    GeneratorIterator& operator++() 
    { 
     assert(myOwner != nullptr); 
     myOwner->advance(); 
     if (myOwner->done()) { 
      myOwner = nullptr; 
     } 
    } 

    PostIncrProxy operator++(int) 
    { 
     return PostIncrProxy(this); 
    } 
}; 

Это будет отложить фактическое приращение до конца полных выражение, которое, я полагаю, может быть проблемой в некоторых дегенеративных случаях, но я бы хотя бы попробовал. (Если хуже приходит к худшему, вы также можете сделать приращение PostIncrProxy::operator* после того, как вы восстановили значение возврата , а затем установить его myOwner нулевое значение, тестирование для случая в деструкторе.)

+0

Проблема в том, что даже низкий уровень 'InputIterator' требует' auto x = * it ++ 'для Работа. – Lucretiel

+0

@ Lucretiel Да, но это не очень сложно. Классическое решение состоит в том, чтобы сохранить копию значения в итераторе (например, 'istream_iterator'), но вы, вероятно, можете обойти это, получив' it ++ 'возврат прокси-сервера, который правильно обрабатывает '*', и делает фактическое добавление в своем деструкторе. (Я думаю, что это на самом деле соответствует, это объясняет, почему стандарт указывает '* r ++', а не только '* a' и' r ++ 'в таблице 107.) –

+0

@Lucretiel Я только что отредактировал свой ответ с грубым что я имел в виду. Я думаю, что это соответствует, но я не уверен, что этот стандарт ясен. –

Смежные вопросы