Pregunta

Yo estoy convencido de que en un proyecto en el que estoy trabajando enteros son la mejor opción en la mayoría de los casos, aunque el valor contenido dentro nunca puede ser negativo.(Más simple inversa para los bucles, menor es la probabilidad de errores, etc., en particular, para los números enteros que sólo puede tener valores entre 0 y, digamos, 20, de todos modos.)

La mayoría de los lugares donde esto va mal es una iteración simple de un std::vector, a menudo se utiliza un array en el pasado y se ha cambiado a un std::vector más tarde.Así que estos bucles en general este aspecto:

for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }

Debido a que este patrón se utiliza tan a menudo, la cantidad de advertencia del compilador de spam acerca de esta comparación entre con y sin signo tipo tiende a ocultar más advertencias útiles.Tenga en cuenta que nosotros definitivamente no tienen vectores con más de INT_MAX elementos, y tenga en cuenta que hasta ahora hemos utilizado dos maneras de solucionar advertencia del compilador:

for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }

Esto generalmente funciona pero podría silencio romperse si el circuito contiene ningún código gusta 'si (i-1 >= 0) ...", etc.

for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }

Este cambio no tiene ningún tipo de efectos secundarios, pero hace que el bucle mucho menos legible.(Y es más escribiendo.)

Así que se me ocurrió la siguiente idea:

template <typename T> struct vector : public std::vector<T>
{
    typedef std::vector<T> base;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }
    int capacity() const { return base::capacity(); }

    vector()                  : base() {}
    vector(int n)             : base(n) {}
    vector(int n, const T& t) : base(n, t) {}
    vector(const base& other) : base(other) {}
};

template <typename Key, typename Data> struct map : public std::map<Key, Data>
{
    typedef std::map<Key, Data> base;
    typedef typename base::key_compare key_compare;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }

    int erase(const Key& k) { return base::erase(k); }
    int count(const Key& k) { return base::count(k); }

    map() : base() {}
    map(const key_compare& comp) : base(comp) {}
    template <class InputIterator> map(InputIterator f, InputIterator l) : base(f, l) {}
    template <class InputIterator> map(InputIterator f, InputIterator l, const key_compare& comp) : base(f, l, comp) {}
    map(const base& other) : base(other) {}
};

// TODO: similar code for other container types

Lo que ves es básicamente la STL clases con los métodos que devuelven size_type reemplazado regresar 'int'.Los constructores son necesarios, ya que estos no son heredados.

¿Qué pensaría de esto como un desarrollador, si quieres ver una solución de este tipo en una base de código existente?

¿Crees que 'whaa, que están redefiniendo la STL, lo que es un gran WTF!', o piensas que esta es una buena solución simple para evitar errores y aumentar la legibilidad.O tal vez prefieres ver que había pasado (la mitad) de un día o así en el cambio de todos estos bucles para usar std::vector<>::iterator?

(En particular, si esta solución se combinó con la prohibición del uso de tipos unsigned para nada, pero los datos en bruto (por ejemplo,unsigned char) y máscaras de bits.)

¿Fue útil?

Solución

Hice este wiki de la comunidad ... Edítelo. No estoy de acuerdo con el consejo en contra de & Quot; int & Quot; nunca más. Ahora lo veo como no está mal.

Sí, estoy de acuerdo con Richard. Nunca debe usar 'int' como la variable de conteo en un ciclo como esos. La siguiente es la forma en que puede querer hacer varios bucles usando índices (aunque hay pocas razones para hacerlo, ocasionalmente esto puede ser útil).

Adelante

for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
    /* ... */
}

Atrás

Puede hacer esto, que es un comportamiento perfectamente definido:

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* ... */
}

Pronto, con c ++ 1x (próxima versión de C ++) muy bien, puede hacerlo así:

for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}

La disminución por debajo de 0 hará que i se ajuste, porque no está firmado.

Pero sin firmar hará que los errores se cuelen

Eso nunca debería ser un argumento para hacerlo de manera incorrecta (usando 23.1 p5 Container Requirements).

¿Por qué no usar std :: size_t arriba?

El estándar C ++ define en T::size_type, que T, por Container ser algunos std::size_t, que este tipo es un tipo integral sin signo definido por implementación. Ahora, usar i para (std::size_t)-1 arriba permitirá que los errores se cuelen silenciosamente. Si someVector.size() == 0 es menor o mayor que <=>, entonces se desbordará <=>, o ni siquiera alcanzará <=> si <=>. Del mismo modo, la condición del bucle se habría roto por completo.

Otros consejos

No derive públicamente de contenedores STL. Tienen destructores no virtuales que invocan un comportamiento indefinido si alguien elimina uno de sus objetos a través de una base de puntero. Si debe derivar, p. desde un vector, hágalo en privado y exponga las partes que necesita exponer con using declaraciones.

Aquí, usaría un size_t como la variable de bucle. Es simple y legible. El póster que comentó que el uso de un índice int lo expone como n00b es correcto. Sin embargo, el uso de un iterador para recorrer un vector lo expone a usted como un n00b un poco más experimentado, uno que no se da cuenta de que el operador de subíndice para el vector es tiempo constante. (vector<T>::size_type es preciso, pero innecesariamente detallado IMO).

Si bien no creo que " use iteradores, de lo contrario se verá n00b " es una buena solución al problema, derivado de std :: vector parece mucho peor que eso.

Primero, los desarrolladores esperan que el vector sea std: .vector, y el mapa sea std :: map. En segundo lugar, su solución no escala para otros contenedores, o para otras clases / bibliotecas que interactúan con contenedores.

Sí, los iteradores son feos, los bucles de iterador no se pueden leer muy bien y typedefs solo cubre el desorden. Pero al menos, sí escalan, y son la solución canónica.

¿Mi solución? un stl para cada macro. Eso no está exento de problemas (principalmente, es una macro, qué asco), pero transmite el significado. No es tan avanzado como p. este , pero hace el trabajo.

Definitivamente use un iterador. Pronto podrá usar el tipo 'auto', para una mejor legibilidad (una de sus preocupaciones) como esta:

for (auto i = someVector.begin();
     i != someVector.end();
     ++i)

Omitir el índice de

El enfoque más sencillo es eludir el problema mediante el uso de iteradores, gama basada en bucles, o algoritmos:

for (auto it = begin(v); it != end(v); ++it) { ... }
for (const auto &x : v) { ... }
std::for_each(v.begin(), v.end(), ...);

Esta es una buena solución si en realidad no se necesita el valor del índice.También se ocupa de la inversa de bucles fácilmente.

Utilizar un tipo unsigned

Otro enfoque es utilizar el tamaño del contenedor tipo.

for (std::vector<T>::size_type i = 0; i < v.size(); ++i) { ... }

También puede utilizar std::size_t (a partir de <cstddef>).Hay quienes (correctamente) que std::size_t no puede ser el mismo tipo de std::vector<T>::size_type (aunque generalmente lo es).Sin embargo, puede estar seguro de que el contenedor del size_type caben en un std::size_t.Así que todo está bien, a menos que el uso de ciertos estilos para revertir los bucles.Mi estilo preferido para invertir bucle es este:

for (std::size_t i = v.size(); i-- > 0; ) { ... }

Con este estilo, usted puede utilizar con seguridad std::size_t, incluso si se trata de un texto más grande que el std::vector<T>::size_type.El estilo de inversión de los bucles de muestra en algunas de las otras respuestas requieren de fundición a -1 exactamente el tipo de derecho y por lo tanto no se puede utilizar la fácil-a-tipo de std::size_t.

El uso de una firma tipo (¡cuidado!)

Si usted realmente desea utilizar firmado un tipo (o si su guía de estilo prácticamente las demandas de una), como int, usted puede utilizar esta pequeña plantilla de función que comprueba la hipótesis subyacente en las versiones de depuración y hace la conversión explícita, de manera que usted no consigue el compilador mensaje de advertencia:

#include <cassert>
#include <cstddef>
#include <limits>

template <typename ContainerType>
constexpr int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

Ahora se puede escribir:

for (int i = 0; i < size_as_int(v); ++i) { ... }

O a la inversa bucles en la manera tradicional:

for (int i = size_as_int(v) - 1; i >= 0; --i) { ... }

El size_as_int truco es sólo un poco más de tipificación de los lazos con las conversiones implícitas, se obtiene la suposición subyacente comprueba en tiempo de ejecución, que el silencio de la advertencia del compilador con la conversión explícita, se obtiene la misma velocidad de depuración no se construye porque es casi seguro que estar en línea, y la optimización de código objeto no debería ser mayor porque la plantilla no hacer nada de lo que el compilador no estaba haciendo ya de forma implícita.

Estás pensando demasiado el problema.

Es preferible usar una variable size_t, pero si no confías en que tus programadores usarán sin firmar correctamente, ve con el elenco y solo trata con la fealdad. Consigue un pasante para cambiarlos a todos y no te preocupes por eso después de eso. Active las advertencias como errores y no aparecerán nuevos. Sus bucles pueden ser & Quot; feo & Quot; ahora, pero puedes entender eso como las consecuencias de tu postura religiosa en lo firmado con lo no firmado.

vector.size() devuelve un size_t var, así que cambie int a size_t y que debería estar bien.

Richard la respuesta es más correcto, excepto que es mucho trabajo para un circuito simple.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top