¿Deberían usarse calificadores de tipo inútil en los tipos de devolución, para mayor claridad?

Nuestra herramienta de análisis estático se queja de un “calificador de tipo inútil en el tipo de devolución” cuando tenemos prototipos en archivos de encabezado como:

const int foo(); 

Lo definimos de esta manera porque la función devuelve una constante que nunca cambiará, pensando que la API parecía más clara con const en su lugar.

Siento que esto es similar a la inicialización explícita de las variables globales a cero para mayor claridad, a pesar de que el estándar C ya establece que todos los globales se inicializarán a cero si no se inicializan explícitamente. Al final del día, realmente no importa. (Pero la herramienta de análisis estático no se queja de eso).

Mi pregunta es, ¿hay alguna razón por la cual esto podría causar un problema? ¿Deberíamos ignorar los errores generados por la herramienta, o deberíamos aplacar la herramienta al posible costo de una API menos clara y consistente? (Devuelve otras const char* las que la herramienta no tiene problemas).

Por lo general, es mejor que su código describa con la mayor precisión posible lo que está sucediendo. Estás recibiendo esta advertencia porque la const en const int foo(); es básicamente sin sentido. La API solo parece más clara si no sabes qué significa la palabra clave const . No sobrecargues el significado de esa manera; static es lo suficientemente mala como es, y no hay razón para agregar la posibilidad de más confusión.

const char * significa algo diferente que const int does, por lo que su herramienta no se queja de ello. El primero es un puntero a una cadena constante, lo que significa que cualquier código que llame a la función que devuelve ese tipo no debería intentar modificar el contenido de la cadena (podría estar en la ROM, por ejemplo). En este último caso, el sistema no tiene forma de exigir que no realice cambios en el int devuelto, por lo que el calificador no tiene sentido. Un paralelo más cercano a los tipos de retorno sería:

 const int foo(); char * const foo2(); 

lo que provocará que su análisis estático dé la advertencia; agregar un calificador const a un valor de retorno es una operación sin sentido. Solo tiene sentido cuando tienes un parámetro de referencia (o tipo de retorno), como tu ejemplo const char * .

De hecho, acabo de hacer un pequeño progtwig de prueba, y GCC incluso advierte explícitamente sobre este problema:

 test.c:6: warning: type qualifiers ignored on function return type 

Entonces, no es solo su progtwig de análisis estático el que se queja.

Puede utilizar una técnica diferente para ilustrar su intención sin hacer infeliz las herramientas.

 #define CONST_RETURN CONST_RETURN int foo(); 

No tienes un problema con const char * porque eso es declarar un puntero a caracteres constantes, no un puntero constante.

Ignorando el const por ahora, foo() devuelve un valor. Tu puedes hacer

 int x = foo(); 

y asigne el valor devuelto por foo() a la variable x , de forma muy similar a como lo puede hacer

 int x = 42; 

para asignar el valor 42 a la variable x.
Pero no puede cambiar el 42 … o el valor devuelto por foo() . Diciendo que el valor devuelto por foo() no se puede cambiar, al aplicar la palabra clave const al tipo de foo() no se logra nada.

Los valores no pueden ser const ( o restrict , o volatile ). Solo los objetos pueden tener calificadores de tipo.


Contrastar con

 const char *foo(); 

En este caso, foo() devuelve un puntero a un objeto. El objeto apuntado por el valor devuelto puede calificarse como const .

El int es devuelto por copia . Puede ser una copia de una const, pero cuando se asigna a otra cosa, ese algo en virtud del hecho de que es asignable, no puede ser, por definición, una const.

La palabra clave const tiene una semántica específica dentro del lenguaje, mientras que aquí la estás utilizando de manera errónea como un comentario esencialmente. En lugar de agregar claridad, más bien sugiere un malentendido de la semántica del lenguaje.

const int foo() es muy diferente de const char* foo() . const char* foo() devuelve una matriz (generalmente una cadena) cuyo contenido no puede cambiar. Piensa en la diferencia entre:

  const char* a = "Hello World"; 

y

 const int b = 1; 

a sigue siendo una variable y puede asignarse a otras cadenas que no pueden cambiar, mientras que b no es una variable. Asi que

 const char* foo(); const char* a = "Hello World\n"; a = foo(); 

está permitido pero

 const int bar(); const int b = 0; b = bar(); 

no está permitido, incluso con la statement const de bar() .

Sí. Aconsejaría escribir el código “explícitamente”, porque deja claro a cualquier persona (incluido usted) al leer el código lo que usted quiso decir. Está escribiendo código para que otros progtwigdores lo lean , ¡no para complacer los caprichos del comstackdor y las herramientas de análisis estático!

(Sin embargo, debe tener cuidado de que dicho “código innecesario” no genere códigos diferentes).

Algunos ejemplos de encoding explícita que mejoran la legibilidad / mantenibilidad:

  • Coloco corchetes alrededor de porciones de expresiones aritméticas para especificar explícitamente lo que quiero que pase. Esto deja en claro a cualquier lector lo que quise decir y me ahorra tener que preocuparme por (o cometer errores) reglas de precedencia:

     int a = b + c * d / e + f;  // Difícil de leer, es necesario saber la precedencia
     int a = b + ((c * d) / e) + f;  // Fácil de leer, claros cálculos explícitos
    

  • En C ++, si anula una función virtual, en la clase derivada puede declararla sin mencionar “virtual”. ¡Cualquiera que lea el código no puede decir que es una función virtual, que puede ser desastrosamente engañosa! Sin embargo, puede usar la palabra clave virtual de forma segura:

      virtual int MyFunc () 

    y esto deja en claro a cualquiera que lea el encabezado de su clase que este método es virtual. (Este “error de syntax de C ++” se corrige en C # requiriendo el uso de la palabra clave “anular” en este caso; más pruebas si alguien lo necesita que perder la “virtual innecesaria” es una idea realmente mala)

Estos son dos ejemplos claros donde agregar código “innecesario” hará que el código sea más legible y menos propenso a errores.

    Intereting Posts