Como otros señalaron, assert
es el último bastión de defensa contra los errores del programador que nunca debería suceder. Son controles de cordura que, con suerte, no deberían fallar de izquierda a derecha en el momento de su envío.
También está diseñado para omitirse de las versiones de lanzamiento estables, por cualquier motivo que los desarrolladores puedan encontrar útil: estética, rendimiento, lo que quieran. Es parte de lo que separa una compilación de depuración de una compilación de lanzamiento, y por definición, una compilación de lanzamiento carece de tales afirmaciones. Por lo tanto, hay una subversión del diseño si desea lanzar la versión analógica "build build con aserciones en su lugar", que sería un intento de una versión de lanzamiento con una _DEBUG
definición de preprocesador y no NDEBUG
definida; ya no es una versión de lanzamiento.
El diseño se extiende incluso a la biblioteca estándar. Como un ejemplo muy básico entre numerosas, muchas implementaciones de std::vector::operator[]
será assert
una comprobación de validez para asegurarse de que no está comprobando el vector fuera de límites. Y la biblioteca estándar comenzará a funcionar mucho, mucho peor si habilita tales comprobaciones en una versión de lanzamiento. Un punto de referencia del vector
usooperator[]
y un controlador de relleno con tales afirmaciones incluidas en una matriz dinámica antigua simple a menudo mostrará que la matriz dinámica es considerablemente más rápida hasta que deshabilite tales comprobaciones, por lo que a menudo impactan el rendimiento de maneras muy, muy poco triviales. Una verificación de puntero nulo aquí y una verificación fuera de límites allí en realidad puede convertirse en un gasto enorme si tales verificaciones se aplican millones de veces sobre cada cuadro en los bucles críticos que preceden al código, tan simple como desreferenciar un puntero inteligente o acceder a una matriz.
Por lo tanto, es muy probable que desee una herramienta diferente para el trabajo y una que no esté diseñada para omitirse de las versiones de lanzamiento si desea versiones de lanzamiento que realicen tales controles de cordura en áreas clave. Lo más útil que personalmente encuentro es el registro. En ese caso, cuando un usuario informa un error, las cosas se vuelven mucho más fáciles si adjuntan un registro y la última línea del registro me da una gran pista sobre dónde ocurrió el error y cuál podría ser. Luego, al reproducir sus pasos en una compilación de depuración, también podría obtener un error de aserción, y ese error de aserción me da más pistas para optimizar mi tiempo. Sin embargo, dado que el registro es relativamente costoso, no lo uso para aplicar controles de cordura de nivel extremadamente bajo, como asegurarme de que no se acceda a una matriz fuera de los límites en una estructura de datos genérica.
Sin embargo, finalmente, y algo de acuerdo con usted, pude ver un caso razonable en el que es posible que desee entregar a los evaluadores algo parecido a una construcción de depuración durante las pruebas alfa, por ejemplo, con un pequeño grupo de evaluadores alfa que, por ejemplo, firmaron un NDA . Allí podría simplificar las pruebas alfa si le entrega a sus probadores algo más que una compilación de lanzamiento completo con información de depuración adjunta junto con algunas características de depuración / desarrollo, como pruebas que pueden ejecutar y resultados más detallados mientras ejecutan el software. Al menos he visto a algunas grandes compañías de juegos haciendo cosas así por alfa. Pero eso es para algo como alfa o pruebas internas en las que realmente intentas darle a los evaluadores algo más que una versión de lanzamiento. Si realmente está intentando enviar una versión de lanzamiento, entonces, por definición, no debería tener_DEBUG
definido o de lo contrario eso es realmente confuso la diferencia entre una compilación "depuración" y "lanzamiento".
¿Por qué se debe eliminar este código antes del lanzamiento? Las comprobaciones no son una gran pérdida de rendimiento y si fallan definitivamente hay un problema sobre el que preferiría un mensaje de error más directo.
Como se señaló anteriormente, las comprobaciones no son necesariamente triviales desde el punto de vista del rendimiento. Es probable que muchos sean triviales, pero una vez más, incluso la lib estándar los usa y podría afectar el rendimiento de manera inaceptable para muchas personas en muchos casos si, por ejemplo, el recorrido de acceso aleatorio demora std::vector
4 veces más en lo que se supone que es una versión de lanzamiento optimizada debido a sus límites de comprobación que nunca se supone que debe fallar.
En un antiguo equipo, en realidad teníamos que hacer que nuestra matriz y biblioteca de vectores excluyeran algunas afirmaciones en ciertas rutas críticas solo para hacer que las compilaciones de depuración se ejecutaran más rápido, porque esas afirmaciones estaban ralentizando las operaciones matemáticas en un orden de magnitud hasta el punto donde estaba comenzando a exigirnos que esperemos 15 minutos antes de que incluso podamos rastrear el código de interés. Mis colegas en realidad solo querían eliminar elasserts
directamente porque descubrieron que solo hacer eso hizo una gran diferencia. En su lugar, decidimos simplemente hacer que las rutas críticas de depuración las eviten. Cuando hicimos que esas rutas críticas usaran los datos de vector / matriz directamente sin pasar por la verificación de límites, el tiempo requerido para realizar la operación completa (que incluía más que solo matemática de vector / matriz) se redujo de minutos a segundos. Es un caso extremo, pero definitivamente las afirmaciones no siempre son insignificantes desde el punto de vista del rendimiento, ni siquiera cercanas.
Pero también es así como asserts
están diseñados. Si no tuvieran un impacto tan grande en el rendimiento en todos los ámbitos, entonces podría favorecerlo si se diseñaron como algo más que una función de compilación de depuración o podríamos usarlo, vector::at
que incluye la verificación de límites incluso en versiones de lanzamiento y lanzamientos fuera de los límites acceso, por ejemplo (pero con un gran éxito de rendimiento). Pero actualmente considero que su diseño es mucho más útil, dado su enorme impacto en el rendimiento en mis casos, como una función de depuración de solo compilación que se omite cuando NDEBUG
se define. Para los casos en los que he trabajado al menos, hace una gran diferencia para una versión de lanzamiento excluir las comprobaciones de cordura que nunca deberían fallar en primer lugar.
vector::at
vs. vector::operator[]
Creo que la distinción de estos dos métodos es esencial para esto, así como la alternativa: excepciones. vector::operator[]
implementaciones típicamente assert
para asegurarse de que el acceso fuera de límites desencadenará un error fácilmente reproducible al intentar acceder a un vector fuera de límites. Pero los implementadores de la biblioteca hacen esto con el supuesto de que no costará ni un centavo en una versión de lanzamiento optimizada.
Mientras tanto, vector::at
se proporciona que siempre realiza la verificación fuera de límites y lanza incluso en las versiones de lanzamiento, pero tiene una penalización de rendimiento hasta el punto en que a menudo veo mucho más uso de código vector::operator[]
que vector::at
. Gran parte del diseño de C ++ se hace eco de la idea de "pagar por lo que usa / necesita", y muchas personas a menudo prefieren operator[]
, lo que ni siquiera se molesta con los límites de verificación en las versiones de lanzamiento, en función de la idea de que no No es necesario que los límites verifiquen sus versiones de lanzamiento optimizadas. De repente, si las aserciones se habilitaran en las versiones de lanzamiento, el rendimiento de estos dos sería idéntico y el uso del vector siempre terminaría siendo más lento que una matriz dinámica. Por lo tanto, una gran parte del diseño y el beneficio de las afirmaciones se basa en la idea de que se vuelven gratuitas en una versión de lanzamiento.
release_assert
Esto es interesante después de descubrir estas intenciones. Naturalmente, los casos de uso de cada persona serían diferentes, pero creo que encontraría algún uso para el release_assert
que realiza la comprobación y bloqueará el software que muestra un número de línea y un mensaje de error incluso en las versiones de lanzamiento.
Sería para algunos casos oscuros en mi caso donde no quiero que el software se recupere con gracia como lo haría si se lanzara una excepción. Me gustaría que se bloqueara incluso en el lanzamiento en esos casos para que el usuario pueda recibir un número de línea para informar cuando el software encontró algo que nunca debería suceder, aún en el ámbito de las comprobaciones de cordura para errores de programador, no errores de entrada externos como excepciones, pero lo suficientemente barato como para hacerse sin preocuparse por su costo de lanzamiento.
En realidad, hay algunos casos en los que sería preferible un bloqueo duro con un número de línea y un mensaje de error que recuperar con gracia una excepción lanzada que podría ser lo suficientemente barata como para mantenerla en una versión. Y hay algunos casos en los que es imposible recuperarse de una excepción, como un error encontrado al intentar recuperarse de una existente. Allí encontraría un ajuste perfecto para, release_assert(!"This should never, ever happen! The software failed to fail!");
y, naturalmente, sería muy barato, ya que la verificación se realizaría dentro de una ruta excepcional en primer lugar y no costaría nada en las rutas de ejecución normales.