Как избежать дублирования кода, реализующего итераторы const и non-const?
я реализую пользовательский контейнер с интерфейсом, подобным STL. Я должен предоставить обычный итератор и итератор const. Большая часть кода для двух версий итераторов идентична . Как избежать этого дублирования?
например, мой класс контейнера Foo
и я реализации FooIterator
и FooConstIterator
. Оба итератора должны предоставлять такие методы, как operator++()
, которые идентичны.
мой вопрос похож на как я удалить дублирование кода между аналогичными функциями-членами const и non-const?, но ответ на этот вопрос специфичен для методов const и non-const, особенно аксессоров. Я не вижу, как это может обобщить проблему итератора.
Я должен FooIterator
выводим из FooConstIterator
и расширить его с помощью дополнительных методов non-const? Это приводит либо к виртуальным методам, либо к скрытию методов, которые здесь кажутся неуместными.
возможно FooIterator
должен содержать FooConstIterator
. Хотя этот подход сокращает дублирование реализации, он, по-видимому, повторно вводит много стандартных определений методов.
есть ли умный шаблон для генерации двух итераторов из одного определения? Или, возможно, есть способ-содрогнуться-использовать препроцессор, чтобы уничтожить эти почти идентичные классы.
Я попытался посмотреть на мою локальную реализацию STL, чтобы увидеть, как она справляется с этим. Есть так много вспомогательных классов, которые я возникли проблемы с дизайном, но похоже, что функциональность просто дублируется.
в предыдущих проектах мой пользовательский контейнер был построен поверх стандартного контейнера STL, поэтому мне не нужно было предоставлять свои собственные итераторы. В данном случае это не вариант.
5 ответов
[лучший ответ, К сожалению, был удален модератором, потому что это был ответ только по ссылке. Я понимаю, почему ответы только на ссылки не поощряются; удаление его, однако, лишило будущих искателей очень полезной информации. Связь остается стабильной на протяжении более семи лет и продолжает работать на момент написания этой статьи.]
я настоятельно рекомендую оригинальную журнальную статью доктора Добба Мэтта Остерна под названием "Стандартные Библиотекарь: Определение итераторов и итераторов Const" в январе 2001 года. Если эта ссылка пойдет плохо, теперь, когда доктор Добб перестал работать, она также доступна здесь.
чтобы предотвратить удаление этого ответа на замену, я обобщу решение.
идея состоит в том, чтобы реализовать итератор один раз в качестве шаблона, который принимает дополнительный параметр шаблона, логическое значение, которое говорит, является ли это версией const. В любом месте реализации, где версии const и non-const различаются, для выбора правильного кода используется механизм шаблона. Механизм Мэтта Остерна назывался choose
. Выглядело это так:--9-->
template <bool flag, class IsTrue, class IsFalse>
struct choose;
template <class IsTrue, class IsFalse>
struct choose<true, IsTrue, IsFalse> {
typedef IsTrue type;
};
template <class IsTrue, class IsFalse>
struct choose<false, IsTrue, IsFalse> {
typedef IsFalse type;
};
если бы у вас были отдельные реализации для итераторов const и non-const, то реализация const включала бы следующие типы:
typedef const T &reference;
typedef const T *pointer;
и реализация non-const будет иметь:
typedef T &reference;
typedef T *pointer;
но с choose
, вы можете иметь одну реализацию, которая выбирает на основе на дополнительном параметре шаблона:
typedef typename choose<is_const, const T &, T &>::type reference;
typedef typename choose<is_const, const T *, T *>::type pointer;
используя typedefs для базовых типов, все методы итератора могут иметь идентичную реализацию. Смотрите Мэтт Austern по пример.
поскольку C++11/14 вы можете избежать таких маленьких помощников, вывести константу непосредственно из логического шаблона.
constness.h:
#ifndef ITERATOR_H
#define ITERATOR_H
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <iterator>
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 <iostream>
#include <array>
#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 _Myvec>
class _Vector_iterator
: public _Vector_const_iterator<_Myvec>
в дополнение к предложению о том, что вы можете шаблонизировать константу и не-константу, вы также можете уменьшить объем работы, взглянув на импульс.Итератор учебник - который также упоминает то же самое решение.
вы можете использовать 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, которые вы можете предоставить, чтобы сделать макрос прямым.