c++ - Evitar advertencias de variables no utilizadas al usar assert() en una versión de Release
warnings assertions (14)
Algunas veces, una variable local se usa con el único propósito de verificarla en un assert (), como tal,
int Result = Func();
assert( Result == 1 );
Al compilar el código en una compilación Release, assert () s generalmente están deshabilitados, por lo que este código puede producir una advertencia sobre el resultado que se está configurando pero nunca se lee.
Una posible solución es:
int Result = Func();
if ( Result == 1 )
{
assert( 0 );
}
Pero requiere demasiada mecanografía, no es fácil para los ojos y hace que siempre se verifique la condición (sí, el compilador puede optimizar el control, pero aún así).
Estoy buscando una forma alternativa de expresar este assert () de una manera que no cause la advertencia, pero que sea fácil de usar y evite cambiar la semántica de assert ().
(Desactivar la advertencia usando un #pragma en esta región de código no es una opción, y bajar los niveles de advertencia para que desaparezca tampoco es una opción ...).
Ciertamente, utiliza una macro para controlar su definición de afirmación, como "_ASSERT". Entonces, puedes hacer esto:
#ifdef _ASSERT
int Result =
#endif /*_ASSERT */
Func();
assert(Result == 1);
Debe mover la afirmación dentro de la función antes de los valores de retorno. Usted sabe que el valor de retorno no es una variable local sin referencia.
Además, tiene más sentido estar dentro de la función de todos modos, porque crea una unidad autónoma que tiene sus PROPIAS condiciones previas y posteriores.
Lo más probable es que si la función está devolviendo un valor, de todos modos debería estar haciendo algún tipo de comprobación de errores en el modo de liberación en este valor de retorno. Entonces, para comenzar, no debería ser una variable sin referencia.
Editar, pero en este caso la condición de publicación debe ser X (ver comentarios):
Estoy totalmente en desacuerdo con este punto, uno debería ser capaz de determinar la condición de la publicación a partir de los parámetros de entrada y si se trata de una función miembro, cualquier estado del objeto. Si una variable global modifica la salida de la función, entonces la función debería reestructurarse.
Este es un mal uso de assert, en mi humilde opinión. Assert no pretende ser una herramienta de informe de errores, sino que pretende afirmar las condiciones previas. Si el resultado no se usa en otro lugar, no es una condición previa.
La mayoría de las respuestas sugieren el uso del static_cast<void>(expression)
en las compilaciones de Release
para suprimir la advertencia, pero esto en realidad no es óptimo si su intención es realizar verificaciones realmente Debug
-only. Los objetivos de una macro de afirmación en cuestión son:
- Realice comprobaciones en modo de
Debug
- No hacer nada en el modo de
Release
- No emitir advertencias en todos los casos
El problema es que el enfoque de void-cast no logra alcanzar el segundo objetivo. Si bien no hay advertencia, la expresión que haya pasado a su macro de aserción se evaluará . Si usted, por ejemplo, hace una comprobación de variables, probablemente no sea un gran problema. Pero, ¿qué ASSERT(fetchSomeData() == data);
si llama a alguna función en su verificación de aserción como ASSERT(fetchSomeData() == data);
(¿Qué es muy común en mi experiencia)? La función fetchSomeData()
seguirá siendo llamada. Puede ser rápido y simple o puede no serlo.
Lo que realmente necesita no es solo la supresión de advertencias sino, lo que es más importante, la no evaluación de la expresión de verificación de solo depuración. Esto se puede lograr con un truco simple que tomé de una biblioteca especializada de Assert :
void myAssertion(bool checkSuccessful)
{
if (!checkSuccessful)
{
// debug break, log or what not
}
}
#define DONT_EVALUATE(expression) /
{ /
true ? static_cast<void>(0) : static_cast<void>((expression)); /
}
#ifdef DEBUG
# define ASSERT(expression) myAssertion((expression))
#else
# define ASSERT(expression) DONT_EVALUATE((expression))
#endif // DEBUG
int main()
{
int a = 0;
ASSERT(a == 1);
ASSERT(performAHeavyVerification());
return 0;
}
Toda la magia está en la macro DONT_EVALUATE
. Es obvio que, al menos lógicamente, la evaluación de su expresión nunca se necesita dentro de ella. Para reforzar eso, el estándar C ++ garantiza que solo se evaluará una de las ramas del operador condicional. Aquí está la cita:
5.16 Operador condicional [expr.cond]
¿lógica o expresión? expresión: expresión-asignación
Grupo de expresiones condicionales de derecha a izquierda. La primera expresión se convierte contextualmente a bool. Se evalúa y si es verdadero, el resultado de la expresión condicional es el valor de la segunda expresión, de lo contrario la de la tercera expresión. Solo una de estas expresiones es evaluada.
He probado este enfoque en GCC 4.9.0, clang 3.8.0, VS2013 Actualización 4, VS2015 Actualización 4 con los niveles de advertencia más duros. En todos los casos, no hay advertencias y la expresión de comprobación nunca se evalúa en la Release
(de hecho, todo está completamente optimizado). Sin embargo, ten en cuenta que con este enfoque te meterás en problemas muy rápido si pones expresiones que tienen efectos secundarios dentro de la macro de afirmación, aunque esta es una práctica muy mala en primer lugar.
Además, espero que los analizadores estáticos puedan advertir sobre "el resultado de una expresión siempre es constante" (o algo así) con este enfoque. He probado esto con las herramientas de análisis estático clang, VS2013, VS2015 y no recibí advertencias de ese tipo.
Lo más simple es declarar / asignar esas variables solo si los asertos existirán. La macro NDEBUG
se define específicamente si las NDEBUG
no se verán afectadas (hecho así porque -DNDEBUG
es una forma conveniente de deshabilitar la depuración, creo), por lo que esta copia modificada de la respuesta de @ Jardel debería funcionar (ver comentario de @ Adam Peterson en esa respuesta):
#ifndef NDEBUG
int Result =
#endif
Func();
assert(Result == 1);
o, si eso no se ajusta a sus gustos, todo tipo de variantes son posibles, por ejemplo esto:
#ifndef NDEBUG
int Result = Func();
assert(Result == 1);
#else
Func();
#endif
En general, con esto, tenga cuidado de que nunca haya la posibilidad de que se NDEBUG
diferentes unidades de traducción con diferentes estados macro de NDEBUG
, especialmente re. afirma u otro contenido condicional en archivos de encabezado públicos. El peligro es que usted o los usuarios de su biblioteca puedan crear una instancia accidental de una definición diferente de una función en línea de la utilizada en la parte compilada de la biblioteca, violando silenciosamente la regla de una definición y haciendo que el comportamiento del tiempo de ejecución sea indefinido.
No podría dar una mejor respuesta que esta, que aborde ese problema, y muchos más:
Trucos estúpidos en C ++: aventuras en la afirmación
#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif
Podría crear otra macro que le permita evitar el uso de una variable temporal:
#ifndef NDEBUG
#define Verify(x) assert(x)
#else
#define Verify(x) ((void)(x))
#endif
// asserts that Func()==1 in debug mode, or calls Func() and ignores return
// value in release mode (any braindead compiler can optimize away the comparison
// whose result isn''t used, and the cast to void suppresses the warning)
Verify(Func() == 1);
Podrías usar:
Check( Func() == 1 );
E implemente su función Check (bool) como desee. Puede usar assert o lanzar una excepción en particular, escribir en un archivo de registro o en la consola, tener diferentes implementaciones en depurar y liberar, o una combinación de todas.
Si este código está dentro de una función, actúe y devuelva el resultado:
bool bigPicture() {
//Check the results
bool success = 1 != Func();
assert(success == NO, "Bad times");
//Success is given, so...
actOnIt();
//and
return success;
}
Usamos una macro para indicar específicamente cuando algo no se usa:
#define _unused(x) ((void)(x))
Entonces en tu ejemplo, tendrías:
int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy
De esa manera (a) la construcción de producción tiene éxito, y (b) es obvio en el código que la variable no se usa por diseño , no que simplemente se haya olvidado de ella. Esto es especialmente útil cuando los parámetros de una función no se utilizan.
Yo usaría lo siguiente:
#ifdef _DEBUG
#define ASSERT(FUNC, CHECK) assert(FUNC == CHECK)
#else
#define ASSERT(FUNC, CHECK)
#endif
...
ASSERT(Func(), 1);
De esta forma, para la versión de lanzamiento, el compilador ni siquiera necesita producir ningún código para assert.
// Value is always computed. We also call assert(value) if assertions are
// enabled. Value is discarded either way. You do not get a warning either
// way. This is useful when (a) a function has a side effect (b) the function
// returns true on success, and (c) failure seems unlikely, but we still want
// to check sometimes.
template < class T >
void assertTrue(T const &value)
{
assert(value);
}
template < class T >
void assertFalse(T const &value)
{
assert(!value);
}
int Result = Func();
assert( Result == 1 );
Esta situación significa que en el modo de lanzamiento, realmente quieres:
Func();
Pero Func
no es nulo, es decir, devuelve un resultado, es decir, es una consulta .
Presumiblemente, además de devolver un resultado, Func
modifica algo (de lo contrario, ¿por qué molestarse en llamarlo y no usar su resultado?), Es decir, es un comando .
Por el principio de separación de consulta de comando (1), Func
no debe ser un comando y una consulta al mismo tiempo. En otras palabras, las consultas no deberían tener efectos secundarios, y el "resultado" de los comandos debería estar representado por las consultas disponibles en el estado del objeto.
Cloth c;
c.Wash(); // Wash is void
assert(c.IsClean());
Es mejor que
Cloth c;
bool is_clean = c.Wash(); // Wash returns a bool
assert(is_clean);
El primero no te da ninguna advertencia de tu tipo, el último sí.
Entonces, en resumen, mi respuesta es: no escribas código como este :)
Actualización (1): solicitó referencias sobre el Principio de Separación de Comando-Consulta . Wikipedia es bastante informativo. Leí sobre esta técnica de diseño en Object Oriented Software Construction, 2nd Editon de Bertrand Meyer.
Actualización (2): j_random_hacker comenta "OTOH, cada función de" comando "f () que previamente devolvió un valor debe ahora establecer alguna variable last_call_to_f_succeeded o similar". Esto solo es cierto para funciones que no prometen nada en su contrato, es decir, funciones que pueden "tener éxito" o no, o un concepto similar. Con Design by Contract , un número importante de funciones tendrá condiciones posteriores, por lo que después de "Empty ()" el objeto será "IsEmpty ()", y después de "Encode ()" la cadena del mensaje será "IsEncoded ()", con no es necesario verificar De la misma manera, y de forma algo simétrica, no se llama a una función especial "IsXFeasible ()" antes de cada llamada a un procedimiento "X ()"; porque generalmente sabe por diseño que está cumpliendo las condiciones previas de X en el momento de su llamada.
int Result = Func();
assert( Result == 1 );
Result;
Esto hará que el compilador deje de quejarse de que el resultado no se use.
Pero debe pensar en usar una versión de assert que haga algo útil en tiempo de ejecución, como registrar errores descriptivos en un archivo que pueda recuperarse del entorno de producción.