2009-11-21 10 views
5

OK, entonces tengo dos clases (completamente diferentes, proyectos diferentes) que usan iteradores ahora. Uno tiene iterator y reverse_iterator trabajando según lo previsto, y el otro, uno actual tiene iterator y un const_iterator semi-roto (específicamente, porque const_iterator deriva del iterador, el código LinkedList<int>::iterator i = const_list.begin() es válido y le permite modificar la lista definida constante ...) .
Tengo la intención de agregar los cuatro tipos a esta clase ... Si puedo.¿Cómo aplico el principio DRY a los iteradores en C++? (iterador, const_iterator, reverse_iterator, const_reverse_iterator)

¿Cómo procedo a minimizar el código de copiar/pegar y cambiar solo el tipo de devolución? Cree una clase base como base_iterator para heredar? Crear un iterator o const_iterator y heredar de eso? Heredar de std :: class? Si alguno de estos casos es el "mejor" enfoque, ¿qué código va a dónde?
Quizás ninguna de las alternativas sea buena? Estoy perdido aquí, y no puedo encontrar mucho material de referencia.

Cualquier consejo es apreciado, pero tenga en cuenta que soy nuevo en el tema (tanto iteradores como C++ en general, especialmente OOP). Intenté, en vano, estudiar los archivos de cabecera enviados con GCC; no son exactamente el tutorial que estoy buscando.

+0

implementaciones stdlib son, en general, las malas decisiones que aprender, para cualquier idioma. A menudo necesitan tratar con interfaces externas (por ejemplo, el sistema operativo, otros idiomas), pueden necesitar ser compatibles con versiones anteriores del código de la última década, y otros factores que simplemente no se aplican a usted. En resumen: no están escritos con la enseñanza como objetivo. Un buen libro es imprescindible y te servirá mucho mejor. –

Respuesta

3

A veces, la aplicación general de la llamada regla DRY (Don't Repeat Yourself, para aquellos que no están familiarizados) no es el mejor enfoque. Especialmente si eres nuevo en el lenguaje (C++ e iteradores) y OOP en sí (metodología), hay poco beneficio en tratar de minimizar la cantidad de código que necesitas escribir en este momento.

Implementaré los dos iteradores usando el código apropiado para cada uno de ellos. Quizás después de que tenga más experiencia con el lenguaje, las herramientas y las técnicas, y luego retroceda y vea si puede reducir la cantidad de código descompensando el código común.

+0

¿Ha tenido que repetir la definición de DRY tanto que lo hace reflexivamente ahora? : P –

+0

Probablemente un buen consejo, y lo tomé. Hice dos clases base separadas, iterador y const_iterator (ambas subclases de std :: iterator), y reverse_iterator basado en el iterador, y const_reverse_iterator en reverse_iterator. El conteo total de líneas para los iteradores fue ~ 70-75. No está mal para 4 tipos diferentes. Eso excluye la próxima documentación en línea, pero incluye suficiente espacio en blanco para que sea legible.:) – exscape

+0

Si bien "implementar primero, refactorizar más tarde" parece un buen consejo * general *, esta respuesta parece una salida para implementar iteradores, donde debería ser conocido qué código a menudo se duplica. Si bien 'iterator' y' reverse_iterator' pueden tener diferencias significativas en la implementación, 'iterator' y' const_iterator' * deben * ser en su mayoría idénticos (al menos para cualquier implementación correcta). – jamesdlin

0

LinkedList<int>::iterator i = const_list.begin() ¿Cómo es tu método de inicio? Mediante el estudio de la STL se puede ver que los contenedores definen dos de tales métodos con las siguientes firmas:

const_iterator begin() const; 
iterator begin(); 

que no debería tener problemas para conseguir una iterator de un objeto clasificado como const. No creo que DRY se aplique aquí.

+0

Actualmente (de forma incorrecta) ha derivado const_iterator del iterador, por lo que con las sobrecargas constrictas/no const habituales para comenzar, su código aún puede convertir implícitamente cualquier const_iterator a un iterador. –

1

Haga que el iterador se derive de const_iterator en lugar de al revés. Use const_cast de forma adecuada (como un detalle de implementación, no expuesto a los usuarios). Esto funciona muy bien en los casos simples y modelos que "iterators son const_iterators" directamente.

Cuando este comienza para requerir comentarios de aclaración en su código, escriba clases separadas. Puede utilizar macros localizadas para generar un código similar para usted, para evitar la repetición de la lógica:

struct Container { 
#define G(This) \ 
This& operator++() { ++_internal_member; return *this; } \ 
This operator++(int) { This copy (*this); ++*this; return copy; } 

    struct iterator { 
    G(iterator) 
    }; 
    struct const_iterator { 
    G(const_iterator) 
    const_iterator(iterator); // and other const_iterator specific code 
    }; 
#undef G 
}; 

que la macro está en el ámbito/localizado es importante, y, por supuesto, sólo lo uso si en realidad le ayuda — si resulta en un código menos legible para usted, escríbalo explícitamente.

Y sobre iteradores inversos: puede usar std::reverse_iterator para envolver sus iteradores "normales" en muchos casos, en lugar de reescribirlos.

struct Container { 
    struct iterator {/*...*/}; 
    struct const_iterator {/*...*/}; 

    typedef std::reverse_iterator<iterator> reverse_iterator; 
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator; 
}; 
0

Una vez que utilizó el siguiente enfoque:

  1. hacer una common_iterator clase de plantilla
  2. añadir typedefs para "repetidor" y "const_iterator"
  3. añadir a "common_iterator" una toma constructor " iterador "tipo

Para el" iterador "el constructor adicional reemplazará la configuración de copia predeterminada uctor, en mi caso era equivalente al constructor de copia predeterminado.

Por "const_iterator" será un constructor adicional que permite construir "const_iterator" de "repetidor"

3

En realidad es muy simple.

Antes que nada, eche un vistazo a la biblioteca Boost.Iterator.

Segundo: necesita declarar una clase base (está bien explicado en el ejemplo cómo proceder) que será similar a esta.

template <class Value> 
class BaseIterator: boost::iterator_adaptor<...> {}; 

Implementa las operaciones para mover el puntero por allí. Tenga en cuenta que debido a que es una adaptación sobre un iterador ya existente, puede implementarlo con solo unos pocos trazos. Es realmente impresionante.

En tercer lugar, sólo tiene que typedef con la const y las versiones no-const:

typedef BaseIterator<Value> iterator; 
typedef BaseIterator<const Value> const_iterator; 

La biblioteca explicitamente le muestre cómo hacer la versión const_iterator sea construible de la versión iterator.

En cuarto lugar, por lo contrario, hay un reverse_iterator objeto especial, que se basa en un iterador regular y moverse hacia atrás :)

Con todo, una manera muy elegante y sin embargo completamente funcional de definir iteradores en clases personalizadas

Escribo regularmente mis propios adaptadores de contenedor, ¡y no se trata de DRY sino simplemente de ahorrarme algo de mecanografía!

+0

+1 para boost :: iterator_adaptor. Lo uso también y funciona muy bien para mí. – n1ckp

0

Una versión más concreta de what maxim1000 suggested:

#include <type_traits> 

template<typename Container, bool forward> 
class iterator_base 
{ 
public: 
    using value_type = 
     typename std::conditional<std::is_const<Container>::value, 
           const typename Container::value_type, 
           typename Container::value_type>::type; 

    iterator_base() { } 

    // For conversions from iterator to const_iterator. 
    template<typename U> 
    iterator_base(const iterator_base<U, forward>& other) 
    : c(other.c) 
    { 
     // .... 
    } 

    value_type& operator*() const 
    { 
     // ... 
    } 

    iterator_base& operator++() 
    { 
     if (forward) 
     { 
      // ... 
     } 
     else 
     { 
      // ... 
     } 
    } 

    iterator_base& operator++(int) 
    { 
     iterator_base copy(*this); 
     ++*this; 
     return copy; 
    } 

private: 
    Container* c = nullptr; 
    // ... 
}; 

using iterator = iterator_base<self_type, true>; 
using const_iterator = iterator_base<const self_type, true>; 

using reverse_iterator = iterator_base<self_type, false>; 
using const_reverse_iterator = iterator_base<const self_type, false>; 
Cuestiones relacionadas