Подтвердить что ты не робот

Может/должен ли я наследовать от STL-итератора?

Может/должен ли я наследовать от итератора STL для реализации собственного класса итератора? Если нет, почему бы и нет?

4b9b3361

Ответ 1

Короткий ответ

Многие считают, что класс std::iterator не предлагает много по сравнению с обычными псевдонимами типа, и даже немного запутывает их, не указывая явно имена и полагаясь на порядок параметров шаблона. Он устарел на С++ 17 и, вероятно, исчезнет через несколько лет.

Это означает, что вы больше не должны использовать std::iterator. Вы можете прочитать весь пост ниже, если вас интересует полная история (там немного избыточности, так как она была запущена до предложения по устареванию).


Наследственный ответ

Вы можете игнорировать все ниже, если вас не интересует история. Следующие фрагменты даже несколько раз противоречат друг другу.

По состоянию на сегодняшний день (С++ 11/С++ 14), стандарт, по-видимому, подразумевает, что нецелесообразно наследовать от std::iterator для реализации пользовательских итераторов. Вот краткое объяснение: N3931:

Хотя стандарт допустил эту ошибку почти десяток раз, я рекомендую не изображать directory_iterator и recursive_directory_iterator как результат std::iterator, так как это требование привязки к реализациям. Вместо этого они должны быть изображены как имеющие соответствующие typedefs, и оставлять их для разработчиков, чтобы решить, как их обеспечить. (Разница наблюдается для пользователей с is_base_of, а не для того, чтобы они задавали этот вопрос.)

[2014-02-08 Даниэль комментирует и формулирует]

Эта проблема в основном похожа на решение, которое использовалось для устранения требования о выходе из unary_function и друзей, как описано N3198, и я решительно выступаю за этот дух. Я хотел бы добавить, что в принципе все "новые" типы итераторов (такие как итератор с regex) не выводятся из std::iterator.

В документе цитируется N3198, который сам утверждает, что он следует за устаревшим обсуждением в N3145. Причины обесценивания классов, которые существуют только для предоставления typedef, приведены как таковые:

Наш опыт с концепциями дает нам уверенность в том, что редко бывает необходимо зависеть от определенных отношений классов, основанных на базовом классе, если достаточно наличия типов и функций. Новые языковые инструменты позволяют нам даже при отсутствии понятий, поддерживаемых языком, выводить существование типов имен в типах классов, что привело бы к значительно более слабой связи между ними. Другим преимуществом замены наследования связанными типами является тот факт, что это уменьшит количество случаев, когда возникают неоднозначности: это может легко произойти, если тип наследует как от unary_function, так и от binary_function (это имеет смысл, если функтор является и унарным, и двоичным функциональным объектом).

tl; dr: классы, которые предоставляют только typedef, теперь считаются бесполезными. Более того, они увеличивают сцепление, когда они не нужны, являются более подробными и могут иметь нежелательные побочные эффекты в некоторых случаях (см. Предыдущую цитату).


Обновление: проблема 2438 из N4245, кажется, фактически противоречит тому, что я утверждал ранее:

Для удобства LWG девять итераторов STL отображаются как результат std::iterator, чтобы получить их iterator_category/etc. Определения типов. К сожалению (и непреднамеренно) это также налагает наследование, которое можно наблюдать (не только через is_base_of, но и разрешение перегрузки). Это печально, потому что это путает пользователей, которые могут быть введены в заблуждение, думая, что их собственные итераторы должны быть получены из std::iterator, или что перегрузка функций для принятия std::iterator имеет какое-то значение. Это также непреднамеренно, потому что для итераторов контейнеров STL не требуются самые важные итераторы STL std::iterator. (Некоторым даже разрешено быть сырыми указателями.) Наконец, это излишне ограничивает исполнителей, которые, возможно, не хотят получать из std::iterator. (Например, чтобы упростить представления отладчика.)

Подводя итог, я ошибался, @aschepler был прав: его можно использовать, но он определенно не требуется - это тоже не обескураживает. Вся вещь "let remove std::iterator" существует для стандартного не для ограничения стандартных реализаторов библиотек.


Раунд 3: P0174R0предлагает отказаться от std::iterator для возможного удаления в будущем. Предложение уже довольно хорошо объясняет, почему оно должно быть устаревшим, поэтому здесь мы идем:

Длинная последовательность аргументов void гораздо менее понятна читателю, чем просто предоставление ожидаемых typedef в самом определении класса, которое является подходом, используемым текущим рабочим черновиком, следуя шаблону, установленному в С++ 14, где мы отказался от вывода по всей библиотеке функторов из unary_function и binary_function.

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

#include <iterator>

template <typename T>
struct MyIterator : std::iterator<std::random_access_iterator_tag, T> {
   value_type data;  // Error: value_type is not found by name lookup 

   // ... implementations details elided ...
};

Одной из причин ясности было достаточно, чтобы убедить LWG обновить стандартную спецификацию библиотеки, чтобы больше не требовать стандартных адаптеров итераторов как вытекающих из std:: iterator, поэтому дальнейшее использование этого шаблона в самом стандарте не требуется. Поэтому он выглядит как сильный кандидат на усталость.

Это становится немного утомительным, и не все, похоже, согласны, поэтому я позволю вам сделать свои собственные выводы. Если комитет в конечном итоге решит, что std::iterator должен быть устаревшим, тогда это станет ясно, что вы больше не должны его использовать. Обратите внимание, что последующая документация подчеркивает отличную поддержку для удаления std::iterator:

Обновление от Jacksonville, 2016:

Опрос: Отменить iterator для С++ 17??
SF F N A SA
6 10 1 0 0

В приведенных выше результатах опроса SF, F, N, A и SA выступают за Сильное, Для, Нейтральное, Против и Сильное Против.

Обновление от Oulu, 2016:

Опрос: Все еще хотите отказаться от std::iterator?
SF F N A SA
3 6 3 2 0

P0619R1 предлагает удалить std::iterator, возможно, как только С++ 20, а также предлагает усилить std::iterator_traits так что он может автоматически выводить типы difference_type, pointer и reference способом std::iterator, когда они явно не предоставляются.

Ответ 2

Если вы имеете в виду std::iterator: да, это для чего.

Если вы имеете в виду что-то еще: нет, потому что ни один из итераторов STL не имеет деструкторов virtual. Они не предназначены для наследования, а класс, наследующий их, может не очищаться должным образом.

Ответ 3

Если вы говорите о шаблоне std::iterator, то да, вы должны, но я надеюсь, вы поймете, что у него нет функциональности, просто куча typedefs. Про это решение состоит в том, что ваш итератор может быть отправлен на шаблон iterator_traits.

Если, с другой стороны, вы говорите о каком-то конкретном итераторе STL, например vector<T>::iterator или другом, тогда ответ будет звучать НЕТ. Не говоря уже обо всем остальном, вы не знаете наверняка, что это действительно класс (например, тот же vector<T>::iterator можно просто набрать как T*)

Ответ 4

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

Undefined Поведение из-за отсутствия виртуальных деструкторов:
Контейнеры STL и итераторы не должны выступать в качестве базовых классов, так как у них нет виртуальных деструкторов.

Для классов без виртуальных деструкторов, используемых в качестве базового класса, проблема возникает при освобождении от указателя на базовый класс (delete, delete [] и т.д.). Поскольку классы не имеют виртуальных деструкторов, они не могут быть очищены должным образом и приводят к Undefined Поведение.

Можно утверждать, что не нужно было бы удалять итератор полиморфно и, следовательно, нечего делать дальше с использованием итераторов STL, возможно, могут возникнуть некоторые другие проблемы, такие как:

Наследование не может быть вообще возможным:
Все типы итераторов в стандартном контейнере Реализация определена.
Например, std::vector<T>::iterator может быть просто T*. В этом случае вы просто не можете наследовать его.

В стандарте С++ нет положений, требующих сказать, что std::vector<T>::iterator не использовать методы ингибирования наследования для предотвращения деривации. Таким образом, если вы выходите из итератора STL, вы полагаетесь на функцию вашего STL, которая позволяет деривацию. Это делает такую ​​реализацию не переносимой.

Багги поведения, если они не реализованы должным образом:
Считайте, что вы выходите из класса векторного итератора, например:

class yourIterator : std::vector<T>::iterator { ... };

Может существовать функция, которая работает на векторных итераторах,
Для примера:

void doSomething(std::vector<T>::iterator to, std::vector<T>::iterator from);

Так как yourIterator является std::vector<T>::iterator, вы можете вызвать doSomething() в своем классе контейнера, но вы столкнулись с уродливой проблемой Object Slicing. doSomething() должен быть реализован надлежащим шаблоном, чтобы избежать проблема.

Проблемы при использовании стандартных библиотечных алгоритмов:
Предположим, вы используете вывод из векторного итератора, а затем используете стандартный библиотечный алгоритм, например std::transform()

Для Ex:

yourIterator a;
yourIterator b;
...
std::transform( a++, b--, ... );

Постфикс operator ++ возвращает std::vector<T>::iterator, а не yourIterator, что приводит к выбору неправильного шаблона.

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