Могу ли я наследовать от итератора STL? - PullRequest
28 голосов
/ 24 июня 2011

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

Ответы [ 4 ]

41 голосов
/ 06 марта 2014

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

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

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


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

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

На сегодняшний день (C ++ 11 / C ++ 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 s, приведены в следующем виде:

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

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


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

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

Подводя итог, я ошибся, @aschepler был прав: его можно использовать , можно , но это, конечно, не требуется - его тоже не обескураживают. Существует целое «давайте удалим std::iterator», чтобы стандарт не ограничивал реализации стандартных библиотек.


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

Длинная последовательность аргументов void гораздо менее понятна для читателя, чем просто предоставление ожидаемых typedefs в самом определении класса, что является подходом, принятым в текущем рабочем проекте, следуя шаблону, установленному в C ++ 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:

Обновление от Джексонвилля, 2016:

Опрос: Устаревший iterator для C ++ 17 ??
SF F N A SA
-10 1 0 0

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

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

Опрос: Все еще не рекомендуется std::iterator?
SF F N A SA
3 6 3 2 0 * * тысяча сто сорок-девять

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

8 голосов
/ 24 июня 2011

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

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

5 голосов
/ 24 июня 2011

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

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

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

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

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

Стандарт C ++ не содержит положений, требующих, чтобы 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()

Например:

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

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

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

5 голосов
/ 24 июня 2011

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

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

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