¿Por qué son los rangos de iteradores estándar [begin, end) en lugar de [begin, end]?


¿Por qué el Estándar define end() como uno más allá del final, en lugar del final real?

Author: Puppy, 2012-04-01

7 answers

El mejor argumento fácilmente es el hecho por Dijkstra mismo :

  • Desea Que el tamaño del rango de ser una simple diferencia end - comenzar;

  • Incluir el límite inferior es más "natural" cuando las secuencias degeneran a vacías, y también porque la alternativa (excluyendo el límite inferior) requeriría la existencia de un valor centinela "uno-antes-del-principio".

Todavía necesita justificar por qué empiezas a contar en cero en lugar de uno, pero eso no era parte de tu pregunta.

La sabiduría detrás de la convención [begin, end] vale la pena una y otra vez cuando se tiene cualquier tipo de algoritmo que se ocupa de múltiples llamadas anidadas o iteradas a construcciones basadas en rango, que se encadenan naturalmente. Por el contrario, el uso de un rango doblemente cerrado incurriría en código apagado por uno y extremadamente desagradable y ruidoso. Por ejemplo, considere una partición [n0, n1)[n1, n2)[n2,n3). Otro ejemplo es el bucle de iteración estándar for (it = begin; it != end; ++it), que se ejecuta end - begin veces. El código correspondiente sería mucho menos legible si ambos extremos fueran inclusivos, e imagine cómo manejaría los rangos vacíos.

Finalmente, también podemos hacer un buen argumento por qué el conteo debe comenzar en cero: Con la convención semiabierta para rangos que acabamos de establecer, si se le da un rango de elementos N (digamos para enumerar los miembros de una matriz), entonces 0 es el "principio" natural para que pueda escribir el rango como [0, N), sin ningún tipo de compensaciones o correcciones incómodas.

En pocas palabras: el hecho de que no veamos el número 1 en todas partes en los algoritmos basados en rangos es una consecuencia directa y una motivación para la convención [begin, end].

 271
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-04 10:41:40

¿Por qué el Estándar define end() como uno más allá del final, en lugar del final real?

Porque:

  1. Evita el manejo especial para rangos vacíos. Para rangos vacíos, begin() es igual a end() &
  2. Hace que el criterio final sea simple para bucles que iteran sobre los elementos: Los bucles simplemente continúe mientras no se alcance end().
 71
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-01 14:54:12

En realidad, muchas cosas relacionadas con los iteradores de repente tienen mucho más sentido si consideramos que los iteradores no apuntan a los elementos de la secuencia, sino entre, con desreferenciación accediendo al siguiente elemento directamente a él. Entonces el iterador" one past end " de repente tiene sentido inmediato:

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^               ^
   |               |
 begin            end

Obviamente begin apunta al principio de la secuencia, y end apunta al final de la misma secuencia. La desreferenciación begin accede al elemento A, y desreferenciar end no tiene sentido porque no hay ningún elemento derecho a ello. Además, agregar un iterador i en el medio da

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
 begin     i      end

Y de inmediato ver que la gama de elementos de begin a i contiene los elementos de A y B, mientras que la variedad de elementos de i a end contiene los elementos de C y D. Dereferencing i da el derecho del elemento de él, que es el primer elemento de la segunda secuencia.

Incluso el "off-by-one" para reverse los iteradores de repente se vuelven obvios de esa manera: Invertir esa secuencia da:

   +---+---+---+---+
   | D | C | B | A |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
rbegin     ri     rend
 (end)    (i)   (begin)

He escrito los correspondientes iteradores no inversos (base) entre paréntesis a continuación. Ves, el iterador inverso que pertenece a i (que he nombrado ri) todavía puntos entre los elementos B y C. Sin embargo, debido a la inversión de la secuencia, ahora element B está a la derecha de ella.

 67
Author: celtschk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-17 17:03:05

Porque entonces

size() == end() - begin()   // For iterators for whom subtraction is valid

Y no tendrás que hacer cosas incómodas como

// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }

Y no escribirás accidentalmente código erróneo como

bool empty() { return begin() == end() - 1; }    // a typo from the first version
                                                 // of this post
                                                 // (see, it really is confusing)

bool empty() { return end() - begin() == -1; }   // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators

También: ¿Qué devolvería find() si end() apuntara a un elemento válido?
¿Realmente quieres otro miembro llamado invalid() que devuelva un iterador no válido?!
Dos iteradores ya es bastante doloroso...

Oh, y ver esto post relacionado .


También:

Si el end estaba antes del último elemento, ¿cómo insert() ¿en el verdadero final?!

 58
Author: Mehrdad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-01 16:10:59

El modismo iterador de rangos semi-cerrados [begin(), end()) se basa originalmente en la aritmética de punteros para matrices simples. En ese modo de operación, tendría funciones que se pasaron una matriz y un tamaño.

void func(int* array, size_t size)

Convertir a rangos medio cerrados [begin, end) es muy simple cuando tienes esa información:

int* begin;
int* end = array + size;

for (int* it = begin; it < end; ++it) { ... }

Para trabajar con rangos completamente cerrados, es más difícil:

int* begin;
int* end = array + size - 1;

for (int* it = begin; it <= end; ++it) { ... }

Dado que los punteros a matrices son iteradores en C++ (y la sintaxis fue diseñada para permitir esto), es mucho es más fácil llamar a std::find(array, array + size, some_value) que llamar a std::find(array, array + size - 1, some_value).


Además, si trabaja con rangos medio cerrados, puede usar el operador != para verificar la condición final, porque (si sus operadores están definidos correctamente) < implica !=.

for (int* it = begin; it != end; ++ it) { ... }

Sin embargo, no hay una manera fácil de hacer esto con rangos completamente cerrados. Estás atascado con <=.

El único tipo de iterador que soporta operaciones < y > en C++ son los iteradores de acceso aleatorio. Si tuvieras que escribir una <= operador para cada clase de iterador en C++, tendría que hacer que todos sus iteradores sean completamente comparables, y tendría menos opciones para crear iteradores menos capaces (como los iteradores bidireccionales en std::list, o los iteradores de entrada que operan en iostreams) si C++ utiliza rangos completamente cerrados.

 22
Author: Ken Bloom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-01 14:05:22

Con el end() apuntando al final, es fácil iterar una colección con un bucle for:

for (iterator it = collection.begin(); it != collection.end(); it++)
{
    DoStuff(*it);
}

Con end() apuntando al último elemento, un bucle sería más complejo:

iterator it = collection.begin();
while (!collection.empty())
{
    DoStuff(*it);

    if (it == collection.end())
        break;

    it++;
}
 8
Author: Anders Abel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-17 20:12:53
  1. Si un contenedor está vacío, begin() == end().
  2. Los programadores de C++ tienden a usar != en lugar de < (menos que) en condiciones de bucle, por lo tanto tener end() apuntando a una posición fuera del extremo es conveniente.
 0
Author: Andreas DM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-27 15:34:48