2010-08-18 22 views
27

Tengo un stl iterator resultante de un std :: find() y deseo probar si es el último elemento. Una manera de escribir esto es la siguiente:¿Está probando si un iterador apunta al último elemento?

mine *match = someValue; 
vector<mine *> Mine(someContent); 
vector<mine *>::iterator itr = std::find(Mine.begin(), Mine.end(), match); 

if (itr == --Mine.end()) { 
    doSomething; 
} 

Pero me parece que decrementar el final() iterador es buscar problemas, como por ejemplo si el vector no tiene elementos, entonces sería indefinido. Incluso si sé que nunca estará vacío, todavía parece feo. Estoy pensando que quizás rbegin() es el camino a seguir, pero no estoy seguro de la mejor manera de comparar el iterador directo con un iterador inverso.

+4

Algo parece feo aquí. No obstante, sugiero que no es la forma de determinar si 'itr' apunta al último elemento. Lo feo es que necesitas saber si 'itr' apunta al último elemento. –

+0

@John: puede que tengas razón. Tal vez tendría más sentido comprobar usando: 'if (Mine.Length()! = 0 && & Mine [Mine.Length() - 1] == itr)', o algo así. –

+0

Hmm, mi respuesta es fea también. Debería haber sido la primera sugerencia de GMan: 'if (& Mine.back() == itr)'. –

Respuesta

50

hacer esto:

// defined in boost/utility.hpp, by the way 
template <typename Iter> 
Iter next(Iter iter) 
{ 
    return ++iter; 
} 

// first check we aren't going to kill ourselves 
// then check if the iterator after itr is the end 
if ((itr != Mine.end()) && (next(itr) == Mine.end())) 
{ 
    // points at the last element 
} 

Eso es todo. Nunca te da un comportamiento indefinido, funciona en todos los iteradores, buenos días.

Envolver para la diversión:

template <typename Iter, typename Cont> 
bool is_last(Iter iter, const Cont& cont) 
{ 
    return (iter != cont.end()) && (next(iter) == cont.end()) 
} 

El dar:

if (is_last(itr, Mine)) 

Si usted es alérgico a las funciones de utilidad/Niza código de mirar, hacer:

if ((itr != Mine.end()) && (itr + 1 == Mine.end())) 

Pero no puede hacerlo en un iterador de acceso no aleatorio s. Esta funciona con iteradores bidireccionales:

if ((itr != Mine.end()) && (itr == --Mine.end())) 

Y es seguro ya que end() > itr por el primer cheque.

+0

Relativo a los iteradores inversos: 'Mine.rbegin(). Base() == Mine.end();'. Los iteradores inversos almacenan un iterador en el siguiente elemento del que obtienes cuando se desreferencia. Aún necesitaría incrementarlo primero. – UncleBens

+0

Como mencioné en mi comentario al OP, creo que la idea de tener que saber si 'itr' apunta al último elemento es un olor a código. Algo simplemente no está bien con eso. Sin embargo, si realmente necesita hacer esto, así es como lo haría. –

+0

El primero es el más cercano a ser correcto, excepto que elimina las referencias innecesariamente solo para hacer referencia nuevamente. Además, no 'back()' throw si el vector está vacío? –

1

Una mejor manera sería copiar el iterador y luego incrementarlo. Luego puede probar la versión incrementada contra end(). Si tiene cuidado, puede usar un incremento posterior para evitar la necesidad de copiarlo formalmente.

if (++vector<mine*>::iterator(itr) == Mine.end()) 

Si ITR ya podría estar al final:

if (itr == Mine.end() || ++vector<mine*>::iterator(itr) == Mine.end()) 

O, basado en la respuesta de GMan pero un poco más seguro:

if (Mine.Length() == 0 || itr == Mine.End() || &*itr == &Mine.back()) 

acabo fija la última vez, como Estaba equivocado sobre &*.

+0

¿Cómo es eso más seguro? – GManNickG

+0

@GMan: si el objetivo es determinar si el iterador apunta al último elemento válido, este código funcionará de manera coherente. Si existe algún riesgo de que ya apunte más allá de ese último elemento válido, puede cambiar el predicado para probar primero si 'itr == Mine.end()', con un OR. –

+0

@Steven: ¿Quiere decir "mejor" que "funciona con iteradores de avance"? – GManNickG

1

Esto es esencialmente el mismo problema que eliminar un nodo de una lista vinculada individualmente. Tienes que tener dos iteradores, uno que sigue un nodo detrás del otro, de modo que cuando el iterador "adelante" llegue al nodo que deseas eliminar (o cualquier operación, en tu caso el nodo deseado sería el final), el " El siguiente "iterador apunta al nodo anterior (en tu caso, este sería el nodo final).

+0

Sí, eso es algo de lo que incoherentemente aludí cuando dije que "si tiene cuidado, puede usar un incremento posterior para evitar la necesidad de copiarlo formalmente". Siempre que mantenga el valor anterior del iterador, puede aumentar previamente el valor actual y tenerlo disponible incluso mientras opera en el valor anterior. Sin embargo, es un poco complicado configurarlo correctamente. –

5

¿Por qué debe realizar un comportamiento especial solo si el artículo es el último?

¿Qué tal esto.El plan es sólo para comparar la dirección del punto del repetidor con la dirección del último elemento en el contenedor, con una comprobación para asegurarse de que el artículo no es en realidad ya finales (haciendo la caja fuerte back llamada):

if (itr != Mine.end() && &*itr == &Mine.back()) { 
    doSomething; 
} 
+0

Esto es * casi * correcto, en que fallará si 'Mine.length()' es 0. –

+0

@Steven ¿Cómo puede 'Mine.length()' ser 0 si encuentra un iterador válido en el contenedor que no es ¿fin? Por definición, ese iterador apunta a un elemento y, por lo tanto, 'length' no puede ser 0. ¿O extraño algo obvio? –

+0

Bueno, el método 'find' puede devolver' Mine.end() 'si no se encontró nada. –

11

Sí, no es seguro disminuir (o incrementar) end si el vector puede estar vacío. Incluso es algo inseguro hacer lo mismo con un puntero, aunque probablemente salgas con la tuya.

a ser muy seguro, el uso de sustracción y los valores conocidos por ser seguros y válido:

if (Mine.end() - itr == 1) 

Para la compatibilidad con todos los iteradores hacia adelante (como en slist, en contraposición a los iteradores de acceso aleatorio de vector y deque), utilice

if (std::distance(itr, Mine.end()) == 1) 

o si están preocupados con el rendimiento, sino que tenga iteradores bidireccionales (incl. cualquier contenedor C++ 03)

if (itr != Mine.end() && itr == -- Mine.end()) 

o el caso verdaderamente anal de sólo hacia adelante iteradores y O (1) tiempo,

if (itr != Mine.end() && ++ container::iterator(itr) == Mine.end()) 

o si está empeñado en inteligencia para evitar nombrar la clase de iterador,

if (itr != Mine.end() && ++ (Mine.begin() = itr) == Mine.end()) 
+0

Esto debería funcionar, y aunque la aritmética del puntero no es tan clara, al menos esto evita intentar desreferenciar un puntero nulo. –

+0

Cambiaste tu respuesta para empeorar las cosas: ahora solo funciona con vectores. –

+0

@Steven: estaba mal antes. La pregunta solo involucra vectores. La edición no cambió su compatibilidad de todos modos, ya que solo eliminé una operación (universal). – Potatoswatter

3

Si usted lo hace:

if(itr != Mine.end() && itr == --Mine.end()) 

Debería estar bien. Porque si itr no está al final, entonces debe haber al menos 1 elemento en el contenedor y, por lo tanto, el extremo debe producir un resultado de valor cuando se decrementa.

Pero si todavía no te gusta eso, hay muchas formas de hacer algo equivalente, como muestran todas las otras respuestas.

Aquí hay otra alternativa:

if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1) 
+0

El segundo no está mal. –

+0

En la segunda solución si el contenedor es similar a una lista, tendrá que recorrer toda la lista para determinar la distancia. –

2

Aquí hay otra posible solución:

template<class Iterator, class Container> bool is_last(Iterator it, const Container& cont) 
{ 
    // REQUIREMENTS: 
    // the iterator must be a valid iterator for `cont` 
    if(it == cont.end()) 
     return false; // or throw if you prefer 
    return (++it) == cont.end(); 
} 
+0

+1, no vi tu respuesta, lo siento por ponerte de puntillas. Sin embargo, tenemos la mente en la función de la colmena, choca esos cinco. – GManNickG

+2

No creo que todas las rutas de control devuelvan un valor ... debería haber 'return false;' como la última línea de la función, ¿verdad? – rmeador

+0

@John Necesita un retorno de cola falso en su función de plantilla. – WilliamKF

3

primero tendría una forma de determine if an iterator is a reverse one, que era ingeniously shown here:

#include <iterator> 
#include <type_traits> 

template<typename Iter> 
struct is_reverse_iterator : std::false_type { }; 

template<typename Iter> 
struct is_reverse_iterator<std::reverse_iterator<Iter>> 
: std::integral_constant<bool, !is_reverse_iterator<Iter>::value> 
{ }; 

entonces se podría tiene dos sabores para realizar la prueba

template<bool isRev> // for normal iterators 
struct is_last_it 
{ 
    template<typename It, typename Cont> 
    static bool apply(It it, Cont const &cont) 
    { // you need to test with .end() 
     return it != cont.end() && ++it == cont.end(); 
    } 
}; 

template<> // for reverse iterators 
struct is_last_it<true> 
{ 
    template<typename It, typename Cont> 
    static bool apply(It it, Cont const &cont) 
    { // you need to test with .rend() 
     return it != cont.rend() && ++it == cont.rend(); 
    } 
}; 

Y una sola función de interfaz

template<typename It, typename Cont> 
bool is_last_iterator(It it, Cont const &cont) 
{ 
    return is_last_it<is_reverse_iterator<It>::value>::apply(it, cont); 
}; 

A continuación, para cada tipo de iterador (inversa/derecho), puede utilizar la función de interfaz

int main() 
{ 
    std::vector<int> v; 
    v.push_back(1); 

    auto it (v.begin()), ite(v.end()); // normal iterators 
    auto rit(v.rbegin()), rite(v.rend()); // reverse iterators 

    std::cout << is_last_iterator(it, v) << std::endl; 
    std::cout << is_last_iterator(ite, v) << std::endl; 
    std::cout << is_last_iterator(rit, v) << std::endl; 
    std::cout << is_last_iterator(rite, v) << std::endl; 

    return 0; 
} 

en cuenta que algunos aplicación (aparte de std::begin() y std::end() que son lo suficientemente comunes, también incluyen std::rbegin() y std::rend().Siempre que sea posible utilizar este conjunto de funciones en lugar de miembro de .begin() etc.

+0

Una hermosa respuesta desde la perspectiva del lenguaje, pero me gustaría incluir todo este encabezado en todas partes solo para una comprobación simple (tiempo de compilación, curva de aprendizaje, etc.) +1 de todos modos. –

1

Tratando de hacer que esta respuesta tan simple y versátil como sea posible:

if(itr!=Mine.end() && itr== --Mine.end()) 

Si el iterador no es bidireccional,

if(itr!=Min.end() && ++decltype(itr)(itr)==Mine.end()) 

El segundo crea una copia temporal de itr y la incrementa para probar contra el iterador final.

En ambos casos, la primera prueba evita contenedores vacíos para desencadenar una situación indefinida.

Cuestiones relacionadas