2009-04-19 10 views
32

Como una extensión a esta pregunta Are const_iterators faster?, tengo otra pregunta en const_iterators. Cómo eliminar la constness de un const_iterator? Aunque los iteradores son una forma generalizada de punteros, pero const_iterator y iterator s son dos cosas diferentes. Por lo tanto, creo que tampoco puedo usar const_cast<> para encubrir desde const_iterator hasta iterator s.Cómo eliminar la constness de const_iterator?

Un enfoque podría ser que definas un iterador que se mueve hasta el elemento al que apunta const_iterator. Pero esto parece ser un algoritmo de tiempo lineal.

¿Alguna idea de cuál es la mejor manera de lograr esto?

+0

¿Estás utilizando boost :: multi_index? – tstenner

+1

No uso la biblioteca de impulso. –

Respuesta

1

Puede restar el iterador begin() del const_iterator para obtener la posición a la que apunta el const_iterator y luego volver a comenzar con begin() para obtener un iterador sin const. No creo que esto sea muy eficiente para los contenedores no lineales, pero para los lineales, como el vector, esto llevará un tiempo constante.

vector<int> v;                           
v.push_back(0); 
v.push_back(1); 
v.push_back(2); 
v.push_back(3); 
vector<int>::const_iterator ci = v.begin() + 2; 
cout << *ci << endl; 
vector<int>::iterator it = v.begin() + (ci - v.begin()); 
cout << *it << endl; 
*it = 20; 
cout << *ci << endl; 

EDITAR: Este aparece sólo a trabajar para contenedores lineales (de acceso aleatorio).

+0

Eso solo funcionará si tiene un operador adecuado definido para restar iteradores de const iterators. AFAIK no existe tal cosa. – PaulJWilliams

+0

Acabo de probar el código anterior y funciona. :) – marcog

+0

Podría funcionar para el vector (iterador de acceso aleatorio). Puede no funcionar para la lista y otro contenedor. –

14

tiempo Desafortunadamente lineal es la única manera de hacerlo:

iter i(d.begin()); 
advance (i,distance<ConstIter>(i,ci)); 

donde iter y constIter son adecuados typedefs y D es el recipiente sobre el que está iterando.

+5

Se permite (y se hace) que las implementaciones especialicen std :: advance y std :: distance para iteradores de acceso aleatorio, de modo que este pueda ser un tiempo constante para algunos contenedores. –

+5

En realidad, este debería ser un tiempo constante para los iteradores de acceso aleatorio (bien implementados). Ver http://www.aristeia.com/Papers/CUJ_June_2001.pdf. –

+0

Para iteradores de acceso no aleatorio, creo 'iter i (d.begin()); mientras que (ConstIter (i)! = ci) ++ i; 'sería más eficiente. Aún es decepcionante, pero al menos solo se adelanta una vez. Puede utilizar el despacho de etiqueta de tipo iterador para escribir plantillas de funciones que, en efecto, sobrecarguen en el tipo de iterador, al menos suponiendo que los iteradores están etiquetados correctamente. –

3

Puede que esta no sea la respuesta que usted quería, pero algo relacionada.

Supongo que desea cambiar el aspecto al que apunta el iterador. La forma más simple que hago es que const_cast la referencia devuelta en su lugar.

Algo como esto

const_cast<T&>(*it);

+0

Algunas funciones como borrar, etc. requieren un const_iterator, por lo que esto no funcionaría. – tstenner

+0

¿Quieres decir que borrar lleva un iterador no const, ¿verdad? Si ese es el caso, ¿por qué usa const_iterator en primer lugar? la mayor parte del tiempo este tipo de molde que necesitaba era para depurar propers. – leiz

0

puede convertir el puntero del valor iterador const a un puntero no valor const y utilizarla directamente algo como esto

vector<int> v;                           
v.push_back(0); 
v.push_back(1); 
v.push_back(2); 
v.push_back(2); 
vector<int>::const_iterator ci = v.begin() + 2; 
cout << *ci << endl; 
*const_cast<int*>(&(*ci)) = 7; 
cout << *ci << endl; 
+0

Esto "funciona" para 'std :: vector' y otros contenedores con almacenamiento contiguo, pero no para otros contenedores (como' std :: list'). –

2

Creo que esta conversión no es necesario en un buen programa diseñado.

Si necesita hacer esto, intente rediseñar el código.

Como solución alternativa puede hacer a continuación:

typedef std::vector<size_t> container_type; 
container_type v; 
// filling container code 
container_type::const_iterator ci = v.begin() + 3; // set some value 
container_type::iterator i = v.begin(); 
std::advance(i, std::distance<container_type::const_iterator>(v.begin(), ci)); 

Pero estoy piensan que a veces esta conversión es imposible, porque sus algoritmos pueden tener no el acceso a contenedor.

+1

+1 en la refactorización. Además, cuando se usa const_iterators se pretende que sea un hack de rendimiento. –

4

En las respuestas a su publicación anterior, hubo un par de personas, incluida yo, que recomendaron el uso de const_iterators por motivos no relacionados con el rendimiento. Legibilidad, trazabilidad desde la placa de diseño hasta el código ... Usar const_iterators para proporcionar acceso mutante a un elemento no const es mucho peor que nunca usar const_iterators en absoluto. Está convirtiendo su código en algo que solo usted comprenderá, con un diseño peor y un dolor real de mantenimiento. Usar const para descartarlo es mucho peor que no usar const.

Si está seguro de quererlo, la parte buena/mala de C++ es que siempre puede obtener suficiente cuerda para colgarse. Si tu intención es usar const_iterator por problemas de rendimiento, realmente deberías reconsiderarlo, pero si aún quieres despegarte ... bueno, C++ puede proporcionarte el arma que elijas.

En primer lugar, el más simple: si sus operaciones toman los argumentos como const (incluso si aplica internamente const_cast), creo que debería funcionar directamente en la mayoría de las implementaciones (incluso si es probable un comportamiento indefinido).

Si no puede cambiar los funtores, puede abordar el problema desde cualquier lado: proporcione un contenedor iterador no consistente alrededor de los const iterators, o bien proporcione un envoltorio functor const alrededor de los funtores no const.

fachada iterador, el largo camino:

template <typename T> 
struct remove_const 
{ 
    typedef T type; 
}; 
template <typename T> 
struct remove_const<const T> 
{ 
    typedef T type; 
}; 

template <typename T> 
class unconst_iterator_type 
{ 
    public: 
     typedef std::forward_iterator_tag iterator_category; 
     typedef typename remove_const< 
       typename std::iterator_traits<T>::value_type 
      >::type value_type; 
     typedef value_type* pointer; 
     typedef value_type& reference; 

     unconst_iterator_type(T it) 
      : it_(it) {} // allow implicit conversions 
     unconst_iterator_type& operator++() { 
      ++it_; 
      return *this; 
     } 
     value_type& operator*() { 
      return const_cast<value_type&>(*it_); 
     } 
     pointer operator->() { 
      return const_cast<pointer>(&(*it_)); 
     } 
     friend bool operator==(unconst_iterator_type<T> const & lhs, 
       unconst_iterator_type<T> const & rhs) 
     { 
      return lhs.it_ == rhs.it_; 
     } 
     friend bool operator!=(unconst_iterator_type<T> const & lhs, 
       unconst_iterator_type<T> const & rhs) 
     { 
      return !(lhs == rhs); 
     } 
    private: 
     T it_; // internal (const) iterator 
}; 
3

Scott Meyer's article prefiriendo iteradores sobre const_iterators responde a esta. La respuesta de Visage es la única alternativa segura previa a C++ 11, pero en realidad es tiempo constante para iteradores de acceso aleatorio bien implementados y tiempo lineal para otros.

+1

El artículo es estándar anterior a 2003 (desde 2001). Me gustaría ver una revisión actualizada después de los cambios de 2003 en el estándar –

+0

@ DavidRodríguez-dribeas: vea mi respuesta para una solución de complejidad de tiempo constante bien definida para C++ 11 (tres años tarde, ¡pero mejor que nunca! :-RE). –

56

Hay una solución con complejidad de tiempo constante en C++ 11: para cualquier secuencia, asociativo o contenedor asociativo desordenado (incluidos todos los contenedores de la Biblioteca estándar), puede llamar a la función miembro de borrado de rango con un vacío rango:

template <typename Container, typename ConstIterator> 
typename Container::iterator remove_constness(Container& c, ConstIterator it) 
{ 
    return c.erase(it, it); 
} 

Las funciones miembro gama de borrado tienen un par de const_iterator parámetros, pero que devuelven un iterator. Como se proporciona un rango vacío, la llamada a borrar no cambia el contenido del contenedor.

Hat tip to Howard Hinnant and Jon Kalb for this trick.

+2

Sin embargo, necesita acceso al contenedor. – Xeo

+7

@xeo: Bueno, por supuesto. Sería un gran agujero en la seguridad de la const si podría hacer esto sin una referencia no const al contenedor. –

+1

+1. Ultrapedantry: esto funciona para todos los contenedores estándar, ya que todos los contenedores estándar son secuencias o contenedores asociativos o contenedores asociativos desordenados. Pero 'erase' no es realmente parte de los requisitos del contenedor, por lo que no necesariamente debe funcionar para todos los tipos definidos por el usuario que satisfagan los requisitos del contenedor. Ya has dicho esto en la respuesta, pero agrega "asociativo desordenado" a la lista en parens. Tal vez esta pedantería debería aplicarse a su comentario en la respuesta de Visage, donde dijo "todos los contenedores", más que en su respuesta completa. –

0

pensé que sería divertido para llegar a una solución a este que funciona para recipientes que no están en la biblioteca estándar y no incluyen el método de borrado().

Intentar utilizar esto hace que Visual Studio 2013 se cuelgue de la compilación. No incluyo el caso de prueba porque dejarlo a los lectores que pueden descubrir rápidamente la interfaz parece una buena idea; No sé por qué esto se cuelga en compilar. Esto ocurre incluso cuando const_iterator es igual a begin().

// deconst.h 

#ifndef _miscTools_deconst 
#define _miscTools_deconst 

#ifdef _WIN32 
    #include <Windows.h> 
#endif 

namespace miscTools 
{ 
    template < typename T > 
    struct deconst 
    { 

     static inline typename T::iterator iterator (typename T::const_iterator*&& target, T*&& subject) 
     { 
      typename T::iterator && resultant = subject->begin (); 

      bool goodItty = process < 0, T >::step (std::move (target), std::move (&resultant), std::move (subject)); 

     #ifdef _WIN32 
      // This is just my habit with test code, and would normally be replaced by an assert 
      if (goodItty == false) 
      { 
        OutputDebugString ("  ERROR: deconst::iterator call. Target iterator is not within the bounds of the subject container.\n") 
      } 
     #endif 
      return std::move (resultant); 
     } 

    private: 

     template < std::size_t i, typename T > 
     struct process 
     { 
      static inline bool step (typename T::const_iterator*&& target, typename T::iterator*&& variant, T*&& subject) 
      { 
       if ((static_cast <typename T::const_iterator> (subject->begin() + i)) == *target) 
       { 
        (*variant) += i; 
        return true; 
       } 
       else 
       { 
        if ((*variant + i) < subject->end()) 
        { 
         process < (i + 1), T >::step (std::move (target), std::move (variant), std::move (subject)); 
        } 
        else { return false; } 
       } 
      } 
     }; 
    }; 
} 

#endif 
Cuestiones relacionadas