Как избежать дублирования кода, реализующего константные и неконстантные iteratorы?

Я реализую пользовательский контейнер с STL-подобным интерфейсом. Я должен предоставить регулярный iterator и константный iterator. Большая часть кода для двух версий iteratorов идентична. Как я могу избежать этого дублирования?

Например, мой контейнерный class Foo , и я реализую FooIterator и FooConstIterator . Оба iteratorа должны предоставлять такие методы, как operator++() которые идентичны.

Мой вопрос похож на Как удалить дублирование кода между аналогичными функциями const и non-const? , но ответ на этот вопрос специфичен для методов const и non-const, особенно для аксессуаров. Я не вижу, как это может быть обобщено на проблему iteratorа.

Должен ли я иметь FooIterator из FooConstIterator и расширять его дополнительными неконстантными методами? Это либо приводит к скрытию виртуальных методов или методов, которые здесь кажутся неуместными.

Возможно, FooIterator должен содержать FooConstIterator . Хотя этот подход уменьшает дублирование реализации, он, похоже, повторно вводит множество определений методов шаблонов.

Существует ли умная технология шаблона для генерации двух iteratorов из одного определения? Или, может быть, есть способ – содрогнуться – использовать препроцессор, чтобы искоренить эти почти одинаковые classы.

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

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

[Лучший ответ был, к сожалению, удален модератором, потому что это был ответ только для ссылок. Я понимаю, почему ссылки только для ответов не приветствуются; однако его удаление лишило будущих искателей очень полезной информации. Ссылка оставалась стабильной более семи лет и продолжает работать на момент написания этой статьи.]

Я настоятельно рекомендую оригинальную статью доктора Добба в журнале Matt Austern под названием «Стандартный библиотекарь: определение iteratorов и iteratorов» , январь 2001 года. Если эта ссылка ухудшится, теперь, когда доктор Добб прекратил работу, он также доступен здесь .

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

Идея состоит в том, чтобы реализовать iterator один раз в качестве шаблона, который принимает дополнительный параметр шаблона, логическое значение, указывающее, является ли это версией const. В любом месте реализации, где версии const и non-const отличаются, вы используете механизм шаблонов для выбора правильного кода. Механизм Мэтта Аустерна был назван choose . Это выглядело так:

 template  struct choose; template  struct choose { typedef IsTrue type; }; template  struct choose { typedef IsFalse type; }; 

Если у вас были отдельные реализации для iteratorов const и non-const, то реализация const включала бы typedefs следующим образом:

 typedef const T &reference; typedef const T *pointer; 

и неконстантная реализация будет иметь:

 typedef T &reference; typedef T *pointer; 

Но при choose вы можете иметь одну реализацию, которая выбирает на основе дополнительного параметра шаблона:

 typedef typename choose::type reference; typedef typename choose::type pointer; 

Используя typedefs для базовых типов, все методы iteratorа могут иметь идентичную реализацию. См. Полный пример Мэтта Фостерна.

Начиная с C ++ 11/14, вы можете избежать таких маленьких помощников, вывести constness прямо из булева шаблона.

constness.h:

 #ifndef ITERATOR_H #define ITERATOR_H #include  #include  #include  #include  struct dummy_struct { int hello = 1; int world = 2; dummy_struct() : hello{ 0 }, world{ 1 }{ } }; template< class T > class iterable { public: template< bool Const = false > class my_iterator { public: using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; /* deduce const qualifier from bool Const parameter */ using reference = typename std::conditional_t< Const, T const &, T & >; using pointer = typename std::conditional_t< Const, T const *, T * >; protected: pointer i; public: my_iterator( T* _i ) : i{ reinterpret_cast< pointer >( _i ) } { } /* SFINAE enables the const dereference operator or the non const variant depending on bool Const parameter */ template< bool _Const = Const > std::enable_if_t< _Const, reference > operator*() const { std::cout << "Const operator*: "; return *i; } template< bool _Const = Const > std::enable_if_t< !_Const, reference > operator*() { std::cout << "Non-Const operator*: "; return *i; } my_iterator & operator++() { ++i; return *this; } bool operator!=( my_iterator const & _other ) const { return i != _other.i; } bool operator==( my_iterator const & _other ) const { return !( *this != _other ); } }; private: T* __begin; T* __end; public: explicit iterable( T* _begin, std::size_t _count ): __begin{ _begin }, __end{ _begin + _count } { std::cout << "End: " << __end << "\n"; } auto begin() const { return my_iterator< false >{ __begin }; } auto end() const { return my_iterator< false >{ __end }; } auto cbegin() const { return my_iterator< true >{ __begin }; } auto cend() const { return my_iterator< true >{ __end }; } }; #endif 

Это можно использовать с чем-то вроде этого:

 #include  #include  #include "constness.h" int main() { dummy_struct * data = new dummy_struct[ 5 ]; for( int i = 0; i < 5; ++i ) { data[i].hello = i; data[i].world = i+1; } iterable< dummy_struct > i( data, 5 ); using iter = typename iterable< dummy_struct >::my_iterator< false >; using citer = typename iterable< dummy_struct >::my_iterator< true >; for( iter it = i.begin(); it != i.end(); ++it ) { std::cout << "Hello: " << (*it).hello << "\n" << "World: " << (*it).world << "\n"; } for( citer it = i.cbegin(); it != i.cend(); ++it ) { std::cout << "Hello: " << (*it).hello << "\n" << "World: " << (*it).world << "\n"; } delete[] data; } 

STL использует наследование

 template class _Vector_iterator : public _Vector_const_iterator<_myvec> 

В дополнение к предположению о том, что вы можете запланировать постоянство и неконстантность, вы также можете уменьшить объем работы, взглянув на учебник Boost.Iterator, в котором также упоминается одно и то же решение.

Вы можете использовать CRTP и общую базу для «инъекции» методов (но вам все равно придется дублировать ctors в текущем C ++) или просто использовать препроцессор (без необходимости дрожать), легко обрабатывает ctors):

 struct Container { #define G(This) \ This operator++(int) { This copy (*this); ++*this; return copy; } // example of postfix++ delegating to ++prefix struct iterator : std::iterator<...> { iterator& operator++(); G(iterator) }; struct const_iterator : std::iterator<...> { const_iterator& operator++(); G(const_iterator) }; #undef G // G is "nicely" scoped and treated as an implementation detail }; 

Используйте std :: iterator, typedefs, который он дает вам, и любые другие typedefs, которые вы могли бы предоставить, чтобы сделать макрос прямолинейным.

  • Разница между Iterator и Listiterator?
  • Как связаны iteratorы и указатели?
  • Как вернуть iterator фильтра из функции?
  • iterator стирания вектора
  • Как избежать «ConcurrentModificationException» при удалении элементов из «ArrayList» при его повторении?
  • C ++ диапазон / эквивалент xrange в STL или boost?
  • Использование iteratorа для разделения массива на части с неравным размером
  • Найти позицию элемента в C ++ 11 для цикла?
  • Как написать цикл for в bash
  • Получите обратный iterator с iteratorа вперед, не зная тип значения
  • Пользовательский Iterator в C ++
  • Interesting Posts

    Создайте новый экземпляр classа с зависимостями, не понимая поставщика фабрики

    Ошибка обнаружения: поврежденные данные JPEG: преждевременный конец сегмента данных

    Обнаружение столкновения сложными формами

    Как торрент-клиенты обходят брандмауэр маршрутизатора?

    Соглашение об именовании C # для свойства enums и соответствия

    Существует ли полная лицензия Windows 8 на продажу? (Не OEM, не обновление)

    Eigen и std :: vector

    Разница MySql между двумя временными метками в днях?

    Как разместить треугольники стрелки на линиях SVG?

    ASP.NET GridView RowIndex как CommandArgument

    Как обновить значение ячейки в таблице дБ, используя SQL Server CE и C # (Visual Studio 2010)

    Rails 3.1 Активы – странное обслуживание в разработке

    Может ли GPU использоваться для кодирования h264?

    Почему в Chrome отображается журнал «Медленная сеть …»?

    Автоматически изменять размер canvasа, чтобы заполнить входящий родитель

    Давайте будем гением компьютера.