c++ - program - vector:: en vs. vector:: operador
vector in c++ stl (8)
Sé que at()
es más lento que []
debido a su comprobación de límites, que también se discute en preguntas similares, como C ++ Vector at / [] operator speed o :: std :: vector :: at () vs operator [] < <resultados sorprendentes! ¡De 5 a 10 veces más lento / más rápido! . Simplemente no entiendo para qué es bueno el método at()
.
Si tengo un vector simple como este: std::vector<int> v(10);
y decido acceder a sus elementos usando at()
vez de []
en una situación en la que tengo un índice i
no estoy seguro si está dentro de los límites de los vectores, me obliga a ajustarlo con el bloque try-catch :
try
{
v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
...
}
aunque puedo hacer el mismo comportamiento al usar size()
y verificar el índice por mi cuenta, lo que me parece más fácil y conveniente:
if (i < v.size())
v[i] = 2;
Entonces mi pregunta es:
¿Cuáles son las ventajas de usar vector::at sobre vector::operator[] ?
¿Cuándo debería usar vector::at lugar de vector::size + vector::operator[] ?
¿Cuáles son las ventajas de usar vector :: en sobre vector :: operador []? ¿Cuándo debería usar vector :: at en lugar de vector :: size + vector :: operator []?
El punto importante aquí es que las excepciones permiten la separación del flujo normal de código de la lógica de manejo de errores, y un único bloque catch puede manejar problemas generados desde cualquiera de miles de sitios de lanzamiento, incluso si se encuentran dispersos en las llamadas a funciones. Por lo tanto, no es que at()
sea necesariamente más fácil para un solo uso, pero a veces resulta más fácil y menos ofuscante para la lógica de caso normal, cuando tiene que indexar mucho para validar.
También es digno de mención que en algunos tipos de código, un índice se incrementa de manera compleja y se usa continuamente para buscar una matriz. En tales casos, es mucho más fácil garantizar las verificaciones correctas con at()
.
Como un ejemplo del mundo real, tengo un código que convierte a C ++ en elementos léxicos, luego otro código que mueve un índice sobre el vector de tokens. Dependiendo de lo que encuentre, es posible que desee incrementar y verificar el siguiente elemento, como en:
if (token.at(i) == Token::Keyword_Enum)
{
ASSERT_EQ(tokens.at(++i), Token::Idn);
if (tokens.at(++i) == Left_Brace)
...
or whatever
En este tipo de situaciones, es muy difícil verificar si ha llegado de manera inapropiada al final de la entrada porque eso depende mucho de los tokens encontrados. La verificación explícita en cada punto de uso es dolorosa, y hay mucho más espacio para el error del programador como incrementos pre / post, compensaciones en el punto de uso, razonamiento defectuoso sobre la validez continua de alguna prueba anterior, etc.
me obliga a envolverlo con un bloque try-catch
No, no (el bloque try / catch puede estar en sentido ascendente). Es útil cuando desea lanzar una excepción en lugar de su programa para ingresar en un dominio de comportamiento indefinido.
Estoy de acuerdo en que la mayoría de los accesos fuera de límites a los vectores son un error del programador (en cuyo caso debe usar assert
para localizar esos errores más fácilmente; la mayoría de las versiones de depuración de las bibliotecas estándar lo hacen automáticamente). No desea usar excepciones que puedan eliminarse aguas arriba para informar errores del programador: desea poder reparar el error .
Dado que es poco probable que un acceso fuera de límites a un vector sea parte del flujo normal del programa (en este caso, tiene razón: verifique de antemano el size
lugar de permitir que salte la excepción), estoy de acuerdo con su diagnóstico : at
es esencialmente inútil.
De acuerdo con this artículo, aparte del rendimiento, no hace ninguna diferencia el uso at
o el operator[]
, solo si se garantiza que el acceso está dentro del tamaño del vector. De lo contrario, si el acceso se basa únicamente en la capacidad del vector, es más seguro usarlo at
.
Diría que las excepciones que arroja vector::at()
no están realmente destinadas a ser atrapadas por el código que lo rodea inmediatamente. Son principalmente útiles para detectar errores en tu código. Si necesita comprobar los límites en el tiempo de ejecución porque, por ejemplo, el índice proviene de la entrada del usuario, de hecho es mejor con una instrucción if
. Entonces, en resumen, diseñe su código con la intención de que vector::at()
nunca arroje una excepción, de modo que si lo hace y su programa aborta, es un signo de error. (al igual que un assert()
)
El objetivo de usar excepciones es que su código de manejo de errores puede estar más alejado.
En este caso específico, la entrada del usuario es de hecho un buen ejemplo. Imagine que desea analizar semánticamente una estructura de datos XML que utiliza índices para hacer referencia a algún tipo de recurso que almacena internamente en un std::vector
. Ahora el árbol XML es un árbol, por lo que es probable que desee utilizar recursividad para analizarlo. En el fondo, en la recursión, puede haber una violación de acceso por parte del escritor del archivo XML. En ese caso, generalmente quiere salir de todos los niveles de recursión y simplemente rechazar todo el archivo (o cualquier tipo de estructura "más grosera"). Aquí es donde resulta útil. Puede escribir el código de análisis como si el archivo fuera válido. El código de la biblioteca se ocupará de la detección de errores y puede detectar el error en el nivel aproximado.
Además, otros contenedores, como std::map
, también tienen std::map::at
cual la semántica es ligeramente diferente que std::map::operator[]
: at se puede usar en un mapa const, mientras que el operator[]
no puede . Ahora bien, si quisiera escribir un código agnóstico de contenedor, como algo que podría tratar con const std::vector<T>&
const std::map<std::size_t, T>&
, ContainerType::at
sería su arma de elección.
Sin embargo, todos estos casos suelen aparecer cuando se maneja algún tipo de entrada de datos no validados. Si está seguro de su rango válido, como debería ser normalmente, generalmente puede usar el operator[]
, pero mejor aún, los iteradores con begin()
y end()
.
En primer lugar, no se especifica si at()
o el operator[]
es más lento. Cuando no hay un error en los límites, espero que tengan la misma velocidad, al menos en las compilaciones de depuración. La diferencia es que at()
especifica exactamente qué sucederá si hay un error de límites (una excepción), donde, como en el caso del operator[]
, es un comportamiento indefinido: un bloqueo en todos los sistemas que uso (g ++ y VC ++), al menos cuando se utilizan los indicadores de depuración normales. (Otra diferencia es que una vez que estoy seguro de mi código, puedo obtener un aumento sustancial de velocidad para el operator[]
desactivando la depuración. Si el rendimiento lo requiere, no lo haría a menos que fuera necesario).
En la práctica, at()
raramente es apropiado. Si el contexto es tal que usted sabe que el índice puede no ser válido, es probable que desee la prueba explícita (por ejemplo, para devolver un valor predeterminado o algo así), y si sabe que no puede ser no válido, desea abortar (y si usted no sabe si puede no ser válido o no, le sugiero que especifique la interfaz de su función de manera más precisa). Sin embargo, existen algunas excepciones en las que el índice inválido puede resultar del análisis de los datos del usuario, y el error debería provocar el aborto de toda la solicitud (pero no bajar el servidor); en tales casos, una excepción es apropiada, y at()
lo hará por usted.
at
puede ser más claro si tienes un puntero al vector:
return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);
Dejando de lado el rendimiento, el primero de ellos es el código más simple y claro.
Nota: Parece que algunas personas nuevas están invalidando esta respuesta sin tener la cortesía de decir lo que está mal. La respuesta a continuación es correcta y se puede verificar here .
En realidad, solo hay una diferencia: at
comprueba los límites, el operator[]
no. Esto se aplica a las compilaciones de depuración, así como a las compilaciones de lanzamiento, y esto está muy bien especificado por los estándares. Es así de simple.
Esto lo hace at
un método más lento, pero también es un mal consejo no usar at
. Tienes que mirar los números absolutos, no los números relativos. Puedo apostar con seguridad que la mayor parte de tu código está haciendo operaciones más costosas que at
. Personalmente, trato de usar at
porque no quiero un error desagradable para crear un comportamiento indefinido y colarme en la producción.