verificar revista lecturas horas home gubernamental etica ethos certificacion cdpe acceso c++ iterator const dry

c++ - lecturas - revista ethos



¿Cómo evitar la duplicación de código implementando constadores const y no const? (5)

Además de la sugerencia de que puede templar la constancia y la no constancia, también puede reducir la cantidad de trabajo echando un vistazo al tutorial Boost.Iterator , que también menciona la misma solución.

Estoy implementando un contenedor personalizado con una interfaz similar a STL. Tengo que proporcionar un iterador regular y un iterador const. La mayor parte del código para las dos versiones de los iteradores es idéntico. ¿Cómo puedo evitar esta duplicación?

Por ejemplo, mi clase contenedora es Foo , y estoy implementando FooIterator y FooConstIterator . Ambos iteradores tienen que proporcionar métodos como operator++() que son idénticos.

Mi pregunta es similar a ¿Cómo elimino la duplicación de código entre funciones similares const y non-const member? , pero la respuesta a esa pregunta es específica para los métodos const y non-const, especialmente los accesores. No veo cómo eso podría generalizar el problema del iterador.

¿Debo hacer que FooIterator derive de FooConstIterator y lo extienda con métodos adicionales no constantes? Eso conduce a métodos virtuales o a métodos de ocultación, que parecen inapropiados aquí.

Quizás FooIterator debería contener un FooConstIterator . Aunque ese enfoque reduce la duplicación de la implementación, parece volver a introducir muchas definiciones de métodos repetitivos.

¿Existe una técnica de plantilla inteligente para generar los dos iteradores a partir de una sola definición? O tal vez haya una manera de - temblar - usar el preprocesador para eliminar estas clases casi idénticas.

He intentado ver mi implementación de STL local para ver cómo maneja esto. Hay tantas clases de ayudantes que tengo problemas para asimilar el diseño, pero parece que la funcionalidad está simplemente duplicada.

En proyectos anteriores, mi contenedor personalizado se construyó sobre un contenedor STL estándar, por lo que no tuve que proporcionar mis propios iteradores. Esa no es una opción en este caso.


Desde C ++ 11/14 puede evitar que pequeños ayudantes deduzcan la constancia directamente de una plantilla booleana.

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

Esto se puede usar con algo así:

#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; }


Puede usar CRTP y una base común para "inyectar" métodos (pero todavía tiene que duplicar los ctors en el C ++ actual), o simplemente usar el preprocesador (no se requiere estremecimiento; maneja los ctors fácilmente):

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 };

Use std :: iterator, los typedefs que le proporciona, y cualquier otro typedefs que pueda proporcionar para hacer que la macro sea sencilla.


STL usa herencia

template<class _Myvec> class _Vector_iterator : public _Vector_const_iterator<_Myvec>


[La mejor respuesta fue, desafortunadamente, eliminada por un moderador porque era una respuesta de solo enlace. Entiendo por qué se desalientan las respuestas de solo enlace; eliminarlo, sin embargo, ha robado a los buscadores futuros información muy útil. El enlace se ha mantenido estable durante más de siete años y continúa funcionando en el momento de escribir este documento.]

Recomiendo encarecidamente el artículo original del Dr. Dobb''s Journal, de Matt Austern, titulado "The Standard Librarian: Defining Iterators and Const Iterators" , enero de 2001. Si ese enlace se rompe, ahora que el Dr. Dobb''s ha dejado de funcionar, también está disponible here .

Para evitar que se borre esta respuesta de reemplazo, resumiré la solución.

La idea es implementar el iterador una vez como una plantilla que toma un parámetro de plantilla adicional, un booleano que indica si esta es la versión constante o no. En cualquier lugar de la implementación donde las versiones const y non-const difieran, se usa un mecanismo de plantilla para seleccionar el código correcto. El mecanismo de Matt Austern se llamaba choose . Se veía así:

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; };

Si tuviera implementaciones separadas para constadores const y non-const, entonces la implementación const incluiría typedefs como este:

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

y la implementación no constante tendría:

typedef T &reference; typedef T *pointer;

Pero con choose , puede tener una implementación única que selecciona según el parámetro de plantilla adicional:

typedef typename choose<is_const, const T &, T &>::type reference; typedef typename choose<is_const, const T *, T *>::type pointer;

Al usar typedefs para los tipos subyacentes, todos los métodos de iterador pueden tener una implementación idéntica. Ver el ejemplo completo de Matt Austern.