lenguaje - ¿Por qué C/C++ no es "#pragma una vez" un estándar ISO?
pragma en lenguaje c (9)
¡Actualmente estoy trabajando en un gran proyecto y mantener todos los que incluyen guardias me vuelve loco! Escribirlo a mano es frustrante pérdida de tiempo. Aunque muchos editores pueden generar guardias de inclusión, esto no ayuda mucho:
Editor genera un símbolo de guardia basado en un nombre de archivo. El problema ocurre cuando tienes encabezados con el mismo nombre de archivo en directorios diferentes. Ambos recibirán lo mismo incluir guardia. La inclusión de la estructura de directorios en el símbolo de guardia requeriría un enfoque elegante del editor, ya que las barras inclinadas y las barras diagonales inversas en la macro no son lo mejor.
Cuando tengo que cambiar el nombre de un archivo, también debo cambiar el nombre de todas las guardias de inclusión (en el ifndef, defino, e idealmente, el comentario de endif). Molesto.
El preprocesador está inundado con toneladas de símbolos sin una pista de lo que significan.
Sin embargo, la definición se incluye una vez, el compilador aún abre el encabezado cada vez que se encuentra con la inclusión del encabezado.
Incluir guardias no encajan en espacios de nombres ni plantillas. De hecho, están subvirtiendo espacios de nombres.
Tienes la posibilidad de que tu símbolo de guardia no sea único.
Tal vez fueron una solución aceptable en momentos en que los programas contenían menos de 1000 encabezados en un solo directorio. ¿Pero hoy en día? Es antiguo, no tiene nada que ver con los hábitos modernos de codificación. Lo que más me molesta es que este problema podría resolverse casi por completo mediante la directiva #pragma once. ¿Por qué no es un estándar?
El problema ocurre cuando tienes encabezados con el mismo nombre de archivo en directorios diferentes.
Entonces, ¿tiene dos encabezados que se denominan ice_cream_maker.h
en su proyecto, y ambos tienen una clase llamada ice_cream_maker
definida en ellos que realiza la misma función? ¿O estás llamando a todas las clases en tu sistema?
Sin embargo, la definición se incluye una vez, el compilador aún abre el encabezado cada vez que se encuentra con la inclusión del encabezado.
Edite el código para que no incluya encabezados varias veces.
Para los encabezados dependientes (en lugar del encabezado principal de una biblioteca), a menudo uso protectores de encabezado que son así:
#ifdef FOO_BAR_BAZ_H
#error foo_bar_baz.h multiply included
#else
#define FOO_BAR_BAZ_H
// header body
#endif
- ¿Con qué frecuencia debe agregar un archivo de inclusión a este proyecto? ¿Realmente es tan difícil agregar DIRNAME_FILENAME al guardia? Y siempre hay GUIDs.
- ¿Realmente cambias el nombre de los archivos a menudo? ¿Nunca? Además, poner el GUARD en el #endif es tan molesto como cualquier otro comentario inútil.
- Dudo que los 1000 archivos de cabecera que tu guardia defina sean siquiera un pequeño porcentaje del número de definiciones generadas por las bibliotecas de tu sistema (especialmente en Windows).
- Creo que MSC 10 para DOS (hace más de 20 años) hizo un seguimiento de qué encabezados se incluyeron y si contenían guardias se salteaban si se incluían de nuevo. Esta es tecnología antigua.
espacios de nombres y plantillas no deben abarcar los encabezados. Querido, no me digas que haces esto:
template <typename foo> class bar { #include "bar_impl.h" };
Ya dijiste eso.
Como ya se señaló, C ++ Standard debe tener en cuenta las diferentes plataformas de desarrollo, algunas de las cuales pueden tener limitaciones para que #pragma sea imposible de implementar una vez.
Por otro lado, el soporte para subprocesos no se agregó por la misma razón anterior, pero el nuevo estándar C ++ incluye subprocesos. Y en este último caso, podemos compilar de forma cruzada para una plataforma muy limitada, pero el desarrollo se realiza en una plataforma completa. Dado que GCC es compatible con esta extensión, creo que la verdadera respuesta a su pregunta es que no hay ninguna parte interesada en introducir esta característica en C ++ Standard.
Desde el punto de vista práctico, incluir guardias causó más problemas a nuestro equipo que el incumplimiento de la directiva #pragma una vez. Por ejemplo, GUID en incluir guardias no ayuda en caso de que el archivo esté duplicado y luego se incluyan ambas copias. Cuando usamos solo #pragma una vez, obtenemos un error de definición duplicado y podemos perder tiempo unificando el código fuente. Pero en el caso de incluir guardias, el problema puede requerir una prueba en tiempo de ejecución, por ejemplo, esto sucede si las copias difieren en los argumentos predeterminados para los parámetros de la función.
Evito el uso de incluir guardias. Si tengo que portar mi código a un compilador sin soporte de #pragma una vez, escribiré un script que agregará guardias a todos los archivos de encabezado.
Creo que es una forma correcta de permitir hacer múltiples inclusiones con solo pragma especial y no permitir incluir múltiples de forma predeterminada, por ejemplo:
#pragma allow_multiple_include_this_file
Entonces, ya que preguntaste por qué. ¿Enviaste tu propuesta a los desarrolladores de Standart? :) No soy enviado también. ¿Puede uno ser una razón?
IIRC, #pragma algo no es parte del lenguaje. Y se muestra en la práctica, mucho.
(edit: estoy completamente de acuerdo en que el sistema de inclusión y enlace debería haber sido más un foco para el nuevo estándar, ya que es una de las debilidades más obvias, en ''le hoy día'')
Incluir guardias es definitivamente una molestia, y C debería haber sido originalmente diseñado de tal manera que los encabezados se incluirían una vez por defecto, lo que requiere alguna opción especial para incluir un encabezado varias veces.
Sin embargo, no fue así, y en su mayoría estás atrapado por tener que usar guardias de inclusión. Dicho esto, #pragma once
es bastante compatible, por lo que es posible que puedas salirte con la tuya.
Personalmente, resuelvo su primer problema (también se le llama archivos de inclusión) al agregar un GUID al protector de inclusión. Es feo, y la mayoría de la gente lo odia (así que a menudo me veo forzado a no usarlo en el trabajo), pero tu experiencia muestra que la idea tiene algún valor, incluso si es terriblemente fea (pero otra vez todo es una especie de truco, ¿por qué no ir a porra?):
#ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
#define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
// blah blah blah...
#endif
He oído que los compiladores en realidad no vuelven a abrir los archivos de encabezado que incluyen guardias (han aprendido a reconocer el modismo). No estoy seguro si eso es cierto (o en qué medida es cierto); Nunca lo he medido. Tampoco me preocupo, pero mis proyectos no son tan grandes como para ser un problema.
Mi truco GUID resuelve bastante los ítems 1, 5 y 6. Simplemente vivo con los ítems 2, 3 y 4. En realidad, para el ítem 2 podrías vivir sin cambiar el nombre de la macro de inclusión de guardias cuando cambies el nombre del archivo ya que el GUID asegurará sigue siendo único. De hecho, no hay ninguna razón para incorporar el nombre del archivo con el GUID. Pero sí, la tradición, supongo.
Probablemente pueda evitar colisiones de nombres sin recurrir a cadenas aleatorias configurando el protector de inclusión para que contenga el nombre de la clase y el espacio de nombres que está en el archivo.
Además, #pragma una vez es compatible con los compiladores de MS y GCC desde hace bastante tiempo, entonces ¿por qué te molesta que no esté en el estándar ISO?
Una solución pragmática
1) elija una política de nombres de guardia coherente (por ejemplo, ruta relativa al nombre del archivo raíz del proyecto +, o cualquier otra cosa que elija). Incluye posibles excepciones para código de terceros.
2) escribir un programa (un simple script de python) para recorrer recursivamente el árbol fuente y verificar que todos los guardias se ajustan a la política. Y cada vez que las protecciones son incorrectas, da salida a un script de diff (o sed, o cualquier otra cosa) que el usuario pueda aplicar fácilmente para corregir. O simplemente solicite una confirmación y realice cambios desde el mismo programa.
3) hacer que todos en el proyecto lo usen (por ejemplo, antes de enviarlo al control de fuente).
Una directiva como #pragma once
no es trivial para definir de una manera totalmente portátil que tiene claros beneficios inequívocos. Algunos de los conceptos para los que plantea preguntas no están bien definidos en todos los sistemas que soportan C
, y su definición de una manera simple podría no proporcionar ningún beneficio sobre los resguardos de inclusión convencionales.
Cuando la compilación encuentra #pragma once
, ¿cómo debería identificar este archivo para que no vuelva a incluir su contenido?
La respuesta obvia es la ubicación única del archivo en el sistema. Esto está bien si el sistema tiene ubicaciones únicas para todos los archivos, pero muchos sistemas proporcionan enlaces (enlaces simbólicos y enlaces duros) que significan que un ''archivo'' no tiene una ubicación única. ¿Debería volver a incluirse el archivo solo porque se encontró con un nombre diferente? Probablemente no.
Pero ahora hay un problema, ¿cómo es posible definir el comportamiento de #pragma once
de una manera que tenga un significado exacto en todas las plataformas, incluso aquellas que ni siquiera tienen directorios, y mucho menos enlaces simbólicos, y aún obtener el deseable comportamiento en sistemas que sí los tienen?
Se podría decir que la identidad de un archivo está determinada por su contenido, por lo que si un archivo incluido tiene un #pragma once
y se incluye un archivo que tiene exactamente el mismo contenido, el segundo y los siguientes #include
s no tendrán efecto.
Esto es fácil de definir y tiene una semántica bien definida. También tiene buenas propiedades, por ejemplo, si un proyecto se mueve desde un sistema que admite y usa enlaces del sistema de archivos a uno que no lo hace, aún se comporta de la misma manera.
En el lado negativo, cada vez que se encuentra un archivo de inclusión que contiene un #pragma once
su contenido debe verificarse con cada otro archivo, use #pragma once
que ya se haya incluido hasta el momento. Esto implica un golpe de rendimiento similar al uso de #include
guardias en cualquier caso y agrega una carga no insignificante a los escritores del compilador. Obviamente, los resultados de esto podrían almacenarse en caché, pero lo mismo es cierto para los guardias de inclusión convencionales.
Los protectores de inclusión convencionales obligan al programador a elegir una macro que es el identificador único para un archivo de inclusión, pero al menos el comportamiento está bien definido y es simple de implementar.
Dadas las posibles dificultades y costos, y el hecho de que los guardias de inclusión convencionales sí funcionan, no me sorprende que el comité de estándares no sienta la necesidad de estandarizar #pragma once
.