¿Es #pragma una vez seguro incluir guardia?

311

He leído que hay algo de optimización del compilador cuando se usa #pragma onceque puede resultar en una compilación más rápida. Reconozco que no es estándar y, por lo tanto, podría plantear un problema de compatibilidad multiplataforma.

¿Es esto algo que es compatible con la mayoría de los compiladores modernos en plataformas que no son de Windows (gcc)?

Quiero evitar problemas de compilación de la plataforma, pero también quiero evitar el trabajo adicional de los guardias alternativos:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

¿Debería Preocuparme? ¿Debo gastar más energía mental en esto?

Ryan Emerle
fuente
3
Después de hacer una pregunta similar , descubrí que #pragma onceparece evitar algunos problemas de vista de clase en VS 2008. Estoy en el proceso de deshacerme de incluir guardias y reemplazarlos #pragma oncepor esta razón.
SmacL

Respuestas:

189

El uso #pragma oncedebería funcionar en cualquier compilador moderno, pero no veo ninguna razón para no usar un #ifndefprotector de inclusión estándar . Funciona bien La única advertencia es que GCC no era compatible #pragma onceantes de la versión 3.4 .

También descubrí que, al menos en GCC, reconoce el #ifndefprotector de inclusión estándar y lo optimiza , por lo que no debería ser mucho más lento que #pragma once.

Zifre
fuente
12
No debería ser más lento (con GCC de todos modos).
Jason Coco el
54
No se implementa de esa manera. En cambio, si el archivo comienza con un #ifndef la primera vez y termina con un #endif, gcc lo recuerda y siempre omite ese incluir en el futuro sin siquiera molestarse en abrir el archivo.
Jason Coco el
10
#pragma oncegeneralmente es más rápido porque el archivo no se está procesando previamente. ifndef/define/endifrequiere preprocesamiento de todos modos, porque después de este bloque puede tener algo compilable (en teoría)
Andrey
13
GCC docs on the guard macro optimization: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian
38
Para usar incluir guardias, existe el requisito adicional de que debe definir un nuevo símbolo como #ifndef FOO_BAR_H, normalmente, para un archivo como "foo_bar.h". Si luego cambia el nombre de este archivo, ¿debería ajustar los protectores de inclusión en consecuencia para que sean consistentes con esta convención? Además, si tiene dos foo_bar.h distintos en dos lugares diferentes en su árbol de códigos, debe pensar en dos símbolos diferentes para cada uno. La respuesta corta es usar #pragma oncey si realmente necesita compilar en un entorno que no lo admite, continúe y agregue protectores de inclusión para ese entorno.
Brandin
329

#pragma once tiene un inconveniente (aparte de no ser estándar) y es que si tiene el mismo archivo en diferentes ubicaciones (lo tenemos porque nuestro sistema de compilación copia archivos), entonces el compilador pensará que son archivos diferentes.

Motti
fuente
36
Pero también puede tener dos archivos con el mismo nombre en diferentes ubicaciones sin tener que molestarse en crear diferentes #define NAMES, que a menudo se presenta en forma de HEADERFILENAME_H
Vargas
69
También puede tener dos o más archivos con el mismo #define WHATEVER, lo que causa un sinfín de diversión, razón por la que preferiría usar pragma una vez.
Chris Huang-Leaver
107
No es persuasivo ... Cambie el sistema de compilación a uno que no copie archivos sino que utilice enlaces simbólicos, o incluya el mismo archivo solo desde una ubicación en cada unidad de traducción. Parece que su infraestructura es un desastre que debe ser reorganizado.
Yakov Galka
3
Y si tiene diferentes archivos con el mismo nombre en diferentes directorios, el enfoque #ifdef pensará que son el mismo archivo. Entonces hay un inconveniente para uno, y hay un inconveniente para el otro.
rxantos
3
@rxantos, si los archivos difieren, el #ifdefvalor macro también puede diferir.
Motti
63

Yo deseo #pragma once (o algo así) haber estado en el estándar. Los guardias de inclusión no son un gran problema (pero parecen ser un poco difíciles de explicar a las personas que aprenden el idioma), pero parece una molestia menor que podría haberse evitado.

De hecho, dado que el 99.98% del tiempo, el #pragma oncecomportamiento es el comportamiento deseado, hubiera sido bueno si el compilador manejara automáticamente la inclusión múltiple de un encabezado, con un#pragma o algo para permitir la doble inclusión.

Pero tenemos lo que tenemos (excepto que es posible que no tenga #pragma once).

Michael Burr
fuente
48
Lo que realmente quiero es una #importdirectiva estándar .
John
10
Se acerca una directiva de importación estándar: isocpp.org/blog/2012/11/… Pero todavía no está aquí. Estoy firmemente a favor de ello.
AHelps
77
@AHelps Vaporware. Han pasado casi cinco años ahora. Tal vez en 2023 vuelvas a este comentario y digas "te lo dije".
doug65536
No es vaporware, pero solo está en la etapa de especificación técnica. Los módulos se implementan en Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) y en clang ( clang.llvm.org/docs/Modules.html ). Y es importación, no #import.
AHelps
Debería convertirse en C ++ 20.
Ionoclast Brigham
36

No conozco ningún beneficio de rendimiento, pero ciertamente funciona. Lo uso en todos mis proyectos de C ++ (siempre que esté usando el compilador de MS). Me parece más efectivo que usar

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Hace el mismo trabajo y no llena el preprocesador con macros adicionales.

GCC admite #pragma onceoficialmente a partir de la versión 3.4 .

JaredPar
fuente
25

GCC admite #pragma oncedesde 3.4, consulte http://en.wikipedia.org/wiki/Pragma_once para obtener más compatibilidad con el compilador.

La gran ventaja que veo al usar #pragma once en lugar de incluir guardias es evitar errores de copiar / pegar.

Seamos realistas: la mayoría de nosotros apenas iniciamos un nuevo archivo de encabezado desde cero, sino que simplemente copiamos uno existente y lo modificamos según nuestras necesidades. Es mucho más fácil crear una plantilla de trabajo usando en #pragma oncelugar de incluir guardias. Cuanto menos tenga que modificar la plantilla, es menos probable que encuentre errores. Tener la misma protección de inclusión en diferentes archivos conduce a errores extraños del compilador y lleva algún tiempo descubrir qué salió mal.

TL; DR: #pragma oncees más fácil de usar.

uceumern
fuente
11

Lo uso y estoy contento con él, ya que tengo que escribir mucho menos para hacer un nuevo encabezado. Me funcionó bien en tres plataformas: Windows, Mac y Linux.

No tengo ninguna información de rendimiento, pero creo que la diferencia entre #pragma y el protector de inclusión no será nada comparado con la lentitud de analizar la gramática de C ++. Ese es el verdadero problema. Intente compilar la misma cantidad de archivos y líneas con un compilador de C #, por ejemplo, para ver la diferencia.

Al final, usar la guardia o el pragma no importará en absoluto.

Edwin Jarvis
fuente
No me gusta #pragma una vez, pero agradezco que señale los beneficios relativos ... El análisis C ++ es mucho más costoso que cualquier otra cosa, en un entorno operativo "normal". Nadie compila desde un sistema de archivos remoto si los tiempos de compilación son un problema.
Tom
1
Re C ++ analizando la lentitud vs. C #. En C # no tiene que analizar (literalmente) miles de LOC de archivos de encabezado (iostream, ¿alguien?) Para cada pequeño archivo C ++. Sin embargo, use encabezados precompilados para hacer que este problema sea más pequeño
Eli Bendersky,
11

Utilizando '#pragma once ' podría no tener ningún efecto (no es compatible en todas partes, aunque es cada vez más compatible), por lo que debe usar el código de compilación condicional de todos modos, en cuyo caso, ¿por qué molestarse con ' #pragma once'? El compilador probablemente lo optimiza de todos modos. Sin embargo, depende de las plataformas de destino. Si todos tus objetivos lo admiten, entonces adelante y úsalo, pero debería ser una decisión consciente porque todo el infierno se desatará si solo usas el pragma y luego lo transfieres a un compilador que no lo admite.

Jonathan Leffler
fuente
1
No estoy de acuerdo con que tengas que apoyar a los guardias de todos modos. Si usa #pragma una vez (o guardias) esto se debe a que genera algunos conflictos sin ellos. Entonces, si su herramienta de cadena no lo admite, el proyecto simplemente no se compilará y usted se encuentra exactamente en el mismo tipo de situación que cuando desea compilar algo de Ansi C en un compilador K&R antiguo. Solo tiene que obtener un chaintool más actualizado o cambiar el código para agregar algunos guardias. Sería un infierno si el programa se estuviera compilando pero no funcionara.
kriss
5

El beneficio de rendimiento es no tener que volver a abrir el archivo una vez que se ha leído el #pragma una vez. Con los guardias, el compilador tiene que abrir el archivo (que puede ser costoso a tiempo) para obtener la información de que no debe incluir su contenido nuevamente.

Esa es la teoría solo porque algunos compiladores no abrirán automáticamente archivos que no tienen ningún código de lectura, para cada unidad de compilación.

De todos modos, no es el caso para todos los compiladores, por lo que idealmente #pragma debe evitarse una vez para el código multiplataforma si no es estándar / no tiene una definición y efecto estandarizados. Sin embargo, prácticamente, es realmente mejor que los guardias.

Al final, la mejor sugerencia que puede obtener para asegurarse de tener la mejor velocidad de su compilador sin tener que verificar el comportamiento de cada compilador en este caso, es usar pragma una vez y guardias.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

De esa manera, obtienes lo mejor de ambos (multiplataforma y velocidad de compilación de ayuda).

Como es más largo de escribir, yo personalmente uso una herramienta para ayudar a generar todo eso de una manera muy fácil (Visual Assist X).

Klaim
fuente
¿Visual Studio no optimiza #include guardias tal cual? Otro compilador (¿mejor?) Lo hace, y me imagino que es bastante fácil.
Tom
1
¿Por qué pones el pragmadespués del ifndef? ¿Hay algún beneficio?
user1095108
1
@ user1095108 Algunos compiladores usarán los protectores de encabezado como delimitador para saber si el archivo contiene solo código que se debe instanciar una vez. Si algún código está fuera de los protectores de encabezado, entonces todo el archivo puede considerarse quizás instanciable más de una vez. Si ese mismo compilador no admite pragma una vez, ignorará esa instrucción. Por lo tanto, colocar el pragma una vez dentro de los protectores de encabezado es la forma más genérica de asegurarse de que al menos los protectores de encabezado se puedan "optimizar".
Klaim
4

No siempre.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 tiene un buen ejemplo de dos archivos destinados a ser incluidos, pero erróneamente se pensó que eran idénticos debido a marcas de tiempo y contenido idénticos (nombre de archivo no idéntico) .

Omer
fuente
10
Eso sería un error en el compilador. (tratando de tomar un atajo que no debería tomar).
rxantos
44
#pragma onceno es estándar, por lo que cualquier cosa que un compilador decida hacer es "correcta". Por supuesto, entonces podemos comenzar a hablar sobre lo que se "espera" y lo que es "útil".
user7610
2

Usar gcc 3.4 y 4.1 en árboles muy grandes (a veces haciendo uso de distcc ), todavía tengo que ver alguna velocidad al usar #pragma una vez en lugar de, o en combinación con guardias de inclusión estándar.

Realmente no veo cómo vale la pena confundir las versiones anteriores de gcc, o incluso otros compiladores, ya que no hay ahorros reales. No he intentado todos los diversos de-linters, pero estoy dispuesto a apostar que confundirá a muchos de ellos.

También deseo que se haya adoptado desde el principio, pero puedo ver el argumento "¿Por qué necesitamos eso cuando ifndef funciona perfectamente bien?". Dadas las muchas esquinas y complejidades oscuras de C, incluir guardias es una de las cosas más fáciles de explicar. Si tiene un pequeño conocimiento de cómo funciona el preprocesador, deberían explicarse por sí mismos.

Sin embargo, si observa una aceleración significativa, actualice su pregunta.

Tim Post
fuente
2

Hoy en día, los guardias de inclusión de la vieja escuela son tan rápidos como un #pragma una vez. Incluso si el compilador no los trata especialmente, se detendrá cuando vea #ifndef WHATEVER y WHATEVER está definido. Abrir un archivo es muy barato hoy. Incluso si hubiera una mejora, sería del orden de milisegundos.

Simplemente no uso #pragma una vez, ya que no tiene ningún beneficio. Para evitar chocar con otros guardias de inclusión, uso algo como: CI_APP_MODULE_FILE_H -> CI = Company Initials; APP = nombre de la aplicación; el resto se explica por sí mismo.

CMircea
fuente
19
¿No es el beneficio que es mucho menos escribir?
Andrey
1
Sin embargo, tenga en cuenta que algunos milisegundos cien mil veces son algunos minutos. Los grandes proyectos consisten en diez miles de archivos que incluyen decenas de encabezados cada uno. Dadas las CPU de muchos núcleos actuales, la entrada / salida, en particular la apertura de muchos archivos pequeños , es uno de los principales cuellos de botella.
Damon
1
"Hoy los guardias de inclusión de la vieja escuela son tan rápidos como un #pragma una vez". Hoy, y también hace muchos años. Los documentos más antiguos en el sitio de GCC son para 2.95 de 2001 y la optimización de incluir guardias no era nueva entonces. No es una optimización reciente.
Jonathan Wakely
44
El principal beneficio es que incluir guardias son propensos a errores y prolijos. Es demasiado fácil tener dos archivos diferentes con nombres idénticos en directorios diferentes (y los protectores de inclusión pueden ser el mismo símbolo), o cometer errores de copiar y pegar mientras se copian los protectores de inclusión. Pragma, una vez, es menos propenso a errores y funciona en todas las principales plataformas de PC. Si puedes usarlo, es mejor estilo.
AHelps
2

La principal diferencia es que el compilador tuvo que abrir el archivo de encabezado para leer el protector de inclusión. En comparación, pragma una vez hace que el compilador realice un seguimiento del archivo y no haga ningún archivo IO cuando se encuentra con otra inclusión para el mismo archivo. Si bien eso puede parecer insignificante, puede ampliarse fácilmente con grandes proyectos, especialmente aquellos sin un buen encabezado que incluyen disciplinas.

Dicho esto, en la actualidad los compiladores (incluido el CCG) son lo suficientemente inteligentes como para incluir a los guardias como pragma una vez. es decir, no abren el archivo y evitan la penalización de IO del archivo.

En los compiladores que no admiten pragma, he visto implementaciones manuales que son un poco engorrosas.

#ifdef FOO_H
#include "foo.h"
#endif

Personalmente, me gusta el enfoque de #pragma una vez, ya que evita la molestia de nombrar colisiones y posibles errores tipográficos. También es un código más elegante en comparación. Dicho esto, para el código portátil, no debería doler tener ambos a menos que el compilador se queje de ello.

Shammi
fuente
1
"Dicho esto, en la actualidad los compiladores (incluido el CCG) son lo suficientemente inteligentes como para incluir a guardias como pragma una vez". Lo han estado haciendo durante décadas, ¡tal vez más de lo que #pragma onceha existido!
Jonathan Wakely
Creo que me has entendido mal. Quise decir antes de pragma una vez, todos los compiladores incurrirían en múltiples penitencias de E / S para el mismo archivo h incluido varias veces durante la etapa de preprocesador. Las implementaciones modernas terminan usando un mejor almacenamiento en caché de archivos en la etapa de preprocesador. En cualquier caso, sin pragmas, la etapa de preprocesador termina incluyendo todo lo que está fuera de los guardias de inclusión. Con pragma una vez, se omite todo el archivo. Desde ese punto de vista, pragma sigue siendo ventajoso.
Shammi
1
No, eso está mal, los compiladores decentes dejan el archivo completo incluso sin #pragma una vez, no abren el archivo por segunda vez y ni siquiera lo miran por segunda vez, vea gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (esto no tiene nada que ver con el almacenamiento en caché).
Jonathan Wakely
1
Desde su enlace, parece que la optimización ocurre solo en cpp. En cualquier caso, el almacenamiento en caché entra en juego. ¿Cómo sabe el preprocesador que incluya código fuera de los guardias? Ejemplo ... extern int foo; #ifndef INC_GUARD #define INC_GUARD class ClassInHeader {}; #endif En este caso, el preprocesador tendrá que incluir extern int foo; varias veces si incluye el mismo archivo varias veces. Al final del día, no tiene mucho sentido discutir sobre esto, siempre y cuando comprendamos la diferencia entre #pragma una vez e incluya guardias y cómo se comportan varios compiladores con ambos :)
Shammi
1
No aplica la optimización en eso, obviamente.
Jonathan Wakely
1

Si usamos msvc o Qt (hasta Qt 4.5), desde GCC (hasta 3.4), ambos soportan msvc #pragma once, no puedo ver ninguna razón para no usarlo #pragma once.

El nombre del archivo fuente generalmente es el mismo nombre de clase igual, y sabemos que a veces necesitamos refactorizar , para cambiar el nombre de la clase, también tuvimos que cambiar el nombre #include XXXX, así que creo que el mantenimiento manual #include xxxxxno es un trabajo inteligente. incluso con la extensión Visual Assist X, mantener el "xxxx" no es un trabajo necesario.

raidsan
fuente
1

Nota adicional para las personas que piensan que siempre se desea una inclusión automática de archivos de encabezado por única vez: construyo generadores de código utilizando la inclusión doble o múltiple de archivos de encabezado desde hace décadas. Especialmente para la generación de stubs de biblioteca de protocolos, me resulta muy cómodo tener un generador de código extremadamente portátil y potente sin herramientas e idiomas adicionales. No soy el único desarrollador que usa este esquema como muestran los blogs X-Macros . Esto no sería posible sin la protección automática que falta.

Marcel
fuente
¿Podrían las plantillas de C ++ resolver el problema? Raramente encuentro una necesidad válida de macros debido a cómo funcionan las plantillas de C ++.
Más claro
1
Nuestra experiencia profesional a largo plazo es que el uso de infraestructura de lenguaje, software y herramientas maduras todo el tiempo nos brinda como proveedores de servicios (sistemas integrados) una gran ventaja en productividad y flexibilidad. En cambio, los competidores que desarrollan software y pilas de sistemas embebidos basados ​​en C ++ pueden encontrar a algunos de sus desarrolladores más felices en el trabajo. Pero generalmente los superamos en tiempo de comercialización, funcionalidad y flexibilidad varias veces. Nether subestima las ganancias de productividad al usar una y la misma herramienta una y otra vez. En cambio, los desarrolladores web sufren de muchas formas de Frameworks.
Marcel
Sin embargo, una nota: no incluye guardias / # pragma una vez en cada archivo de encabezado contra el principio DRY. Puedo ver su punto en la X-Macrofunción, pero no es el uso principal de include, ¿no debería ser al revés como header unguard / # pragma multi si nos quedamos con DRY?
caiohamamura
DRY significa "No te repitas". Se trata de lo humano. Lo que hace la máquina no tiene nada que ver con ese paradigma. Las plantillas de C ++ se repiten mucho, los compiladores de C también lo hacen (por ejemplo, desenrollar bucles) y cada computadora repite casi todo lo increíble con frecuencia y rapidez.
Marcel