Sé que at()
es más lento que []
debido a su verificación de límites, que también se discute en preguntas similares como Vector C ++ a / [] velocidad del operador o :: std :: vector :: at () vs operador [] << ¡resultados sorprendentes! ¡5 a 10 veces más lento / más rápido! . Simplemente no entiendo para qué at()
sirve el método.
Si tengo un vector simple como este: std::vector<int> v(10);
y decido acceder a sus elementos usando en at()
lugar de []
en una situación cuando tengo un índice i
y no estoy seguro si está en los límites de los vectores, me obliga a envolverlo con try-catch bloque :
try
{
v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
...
}
aunque puedo obtener el mismo comportamiento usando size()
y verificando el índice por mi cuenta, lo que parece más fácil y muy conveniente para mí:
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 en lugar de vector :: size + vector :: operator [] ?
if (i < v.size()) v[i] = 2;
, hay una posible ruta de código que no se asigna2
a ningún elemento dev
. Si ese es el comportamiento correcto, genial. Pero a menudo no hay nada sensato que esta función pueda hacer cuandoi >= v.size()
. Por lo tanto, no hay ninguna razón en particular por la que no deba usar una excepción para indicar una situación inesperada. Muchas funciones simplemente se usanoperator[]
sin una verificación del tamaño, documento quei
debe estar dentro del rango, y culpan a la persona que llama por el UB resultante.Respuestas:
Yo diría que las excepciones que
vector::at()
arrojan realmente no están destinadas a ser detectadas por el código que las rodea inmediatamente. Son principalmente útiles para detectar errores en su código. Si necesita verificar los límites en tiempo de ejecución porque, por ejemplo, el índice proviene de la entrada del usuario, es mejor que tenga unaif
declaración. Entonces, en resumen, diseñe su código con la intención de quevector::at()
nunca genere una excepción, de modo que si lo hace, y su programa aborta, sea una señal de un error. (como unassert()
)fuente
size()
+[]
cuando el índice depende de la entrada de los usuarios, usarloassert
en situaciones donde el índice nunca debe estar fuera de los límites para una fácil corrección de errores en el futuro y.at()
en todas las demás situaciones (por si acaso, porque podría suceder algo incorrecto ... .)vector
entonces probablemente sea mejor usarla como la opción "por si acaso" en lugar de enat()
todas partes. De esa manera, puede esperar un poco más de rendimiento en el modo de lanzamiento, en caso de que alguna vez lo necesite.operator[]
, por ejemplo, gcc.gnu.org/onlinedocs/libstdc++/manual/… así que si su plataforma lo admite, ¡probablemente sea mejor que lo haga!No, no lo hace (el bloque try / catch puede estar en sentido ascendente). Es útil cuando desea que se lance una excepción en lugar de que su programa entre en un ámbito de comportamiento indefinido.
Estoy de acuerdo en que la mayoría de los accesos fuera de los límites a los vectores son un error del programador (en cuyo caso debería usarlo
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 utilizar excepciones que se puedan tragar en sentido ascendente para informar errores del programador: desea poder corregir el error .Dado que es poco probable que un acceso fuera de los límites a un vector sea parte del flujo normal del programa (en el caso de que lo sea, tiene razón: verifique de antemano con en
size
lugar de dejar que la excepción burbujee), estoy de acuerdo con su diagnóstico:at
es esencialmente inútil.fuente
out_of_range
excepción, entoncesabort()
se llama.try..catch
puede estar presente en el método que llama a este método.at
es útil en la medida en que de otra manera se encontraría escribiendo algo comoif (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }
. La gente suele pensar en las funciones de lanzamiento de excepciones en términos de "maldiciones, tengo que manejar la excepción", pero siempre que documente con cuidado lo que puede generar cada una de sus funciones, también se pueden usar como "genial, no tienes que comprobar una condición y lanzar una excepción ".out_of_range
deriva delogic_error
, y otros programadores "deberían" saberlo mejor que capturarlogic_error
los mensajes de correo electrónico en sentido ascendente e ignorarlos.assert
También se puede ignorar si tus colegas no quieren conocer sus errores, es más difícil porque tienen que compilar tu códigoNDEBUG
;-) Cada mecanismo tiene sus méritos y defectos.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 solo bloque de captura puede manejar problemas generados desde cualquiera de los innumerables sitios de lanzamiento, incluso si se encuentran dispersos en lo profundo de las llamadas a funciones. Por lo tanto, no
at()
es necesariamente más fácil para un solo uso, sino que a veces se vuelve más fácil, y menos confusa de la lógica del caso normal, cuando tiene mucha indexación 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 comprobaciones correctas utilizando
at()
.Como ejemplo del mundo real, tengo un código que convierte 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:
En este tipo de situación, es muy difícil comprobar si ha llegado de forma inapropiada al final de la entrada porque eso depende mucho de los tokens exactos encontrados. La verificación explícita en cada punto de uso es dolorosa, y hay mucho más margen para el error del programador a medida que entran en juego incrementos previos / posteriores, compensaciones en el punto de uso, razonamiento defectuoso sobre la validez continua de alguna prueba anterior, etc.
fuente
at
puede ser más claro si tiene un puntero al vector:Dejando de lado el rendimiento, el primero de ellos es el código más simple y claro.
fuente
Primero, no se especifica si es
at()
ooperator[]
es más lento. Cuando no hay un error de límites, esperaría que tuvieran aproximadamente la misma velocidad, al menos en las compilaciones de depuración. La diferencia es queat()
especifica exactamente lo que sucederá si hay un error de límites (una excepción), donde, como en el caso deoperator[]
, es un comportamiento indefinido: un bloqueo en todos los sistemas que uso (g ++ y VC ++), al menos cuando se utilizan las banderas de depuración normales. (Otra diferencia es que una vez que estoy seguro de mi código, puedo obtener un aumento sustancial de velocidad paraoperator[]
al desactivar la depuración. Si el rendimiento lo requiere, no lo haría a menos que fuera necesario).En la práctica,
at()
rara vez es apropiado. Si el contexto es tal que sabe que el índice puede no ser válido, probablemente desee la prueba explícita (por ejemplo, que devuelva un valor predeterminado o algo), y si sabe que no puede ser inválido, desea abortar (y si no sabe si puede ser inválido o no, le sugiero que especifique la interfaz de su función con mayor precisión). Sin embargo, hay algunas excepciones en las que el índice no válido puede resultar del análisis de los datos del usuario, y el error debería provocar la interrupción de toda la solicitud (pero no hacer que el servidor caiga); en tales casos, una excepción es apropiada, yat()
lo hará por usted.fuente
operator[]
no está obligado a verificar los límites, mientrasat()
que sí? ¿Está implicando problemas de almacenamiento en caché, especulación y búfer de ramificación?operator[]
no está obligado a verificar los límites, pero todas las buenas implementaciones sí lo hacen. Al menos en modo de depuración. La única diferencia es lo que hacen si el índice está fuera de los límites:operator[]
aborta con un mensaje de error,at()
lanza una excepción.at()
.std::string
que no siempre funcionaba si las opciones de verificación no se correspondían con las del tiempo de ejecución:,-MD
y será mejor que desactive la verificación-MDd
, y será mejor que tenga encendido.)El objetivo de usar excepciones es que su código de manejo de errores puede estar más lejos.
En este caso específico, la entrada del usuario es un buen ejemplo. Imagine que desea analizar semánticamente una estructura de datos XML que utiliza índices para referirse a algún tipo de recurso que almacena internamente en un archivo
std::vector
. Ahora el árbol XML es un árbol, por lo que probablemente desee utilizar la recursividad para analizarlo. En el fondo, en la recursividad, puede haber una infracción de acceso por parte del escritor del archivo XML. En ese caso, normalmente querrá salir de todos los niveles de recursividad y simplemente rechazar todo el archivo (o cualquier tipo de estructura "más burda"). 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 encargará de la detección de errores y usted puede detectar el error en el nivel grueso.Además, otros contenedores, como
std::map
, también tienenstd::map::at
que tiene una semántica ligeramente diferente questd::map::operator[]
: at se puede usar en un mapa constante, mientrasoperator[]
que no. Ahora, si quisieras escribir código independiente de contenedores, como algo que pudiera lidiar conconst std::vector<T>&
oconst std::map<std::size_t, T>&
,ContainerType::at
sería tu arma preferida.Sin embargo, todos estos casos suelen aparecer cuando se maneja algún tipo de entrada de datos no validada. Si está seguro de su rango válido, como debería estarlo normalmente
operator[]
, normalmente puede usar , pero mejor aún, iteradores conbegin()
yend()
.fuente
Según este artículo, dejando de lado el rendimiento, no hay ninguna diferencia en usar
at
ooperator[]
, 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 utilizarloat
.fuente
Nota: Parece que algunas personas nuevas están rechazando esta respuesta sin tener la cortesía de decir qué está mal. La respuesta a continuación es correcta y se puede verificar aquí .
En realidad, solo hay una diferencia:
at
comprueba los límites mientrasoperator[]
que no. Esto se aplica tanto a las versiones de depuración como a las versiones de lanzamiento y esto está muy bien especificado por los estándares. Es así de simple.Esto lo convierte en
at
un método más lento, pero también es un mal consejo que no lo useat
. Tienes que mirar números absolutos, no números relativos. Puedo apostar con seguridad que la mayor parte de su código está realizando operaciones más caras queat
. Personalmente, trato de usarat
porque no quiero que un error desagradable cree un comportamiento indefinido y se cuele en producción.fuente
std::out_of_range
o cualquier forma destd::logic_error
es, de hecho, un error lógico en sí mismo aquí .at
y[]
y mi respuesta simplemente establece la diferencia. Yo personalmente uso el método "seguro" cuando el rendimiento no es un problema. Como dice Knuth, no realice una optimización prematura. Además, es bueno que los errores se detecten antes que en producción, independientemente de las diferencias filosóficas.