¿Cuál es el punto de decirle al compilador específicamente que incluya el archivo solo una vez? ¿No tendría sentido por defecto? ¿Existe alguna razón para incluir un solo archivo varias veces? ¿Por qué no simplemente asumirlo? ¿Tiene que ver con hardware específico?
81
#ifdefs
. Entonces podría decir#define MODE_ONE 1
y luego#include "has-modes.h"
, y luego#undef MODE_ONE
con#define MODE_TWO 1
y#include "has-modes.h"
otra vez. El preprocesador es agnóstico sobre este tipo de cosas, y tal vez a veces pueden tener sentido.<assert.h>
varias veces, con diferentes definiciones deNDEBUG
, en el mismo archivo fuente.#pragma once
sí mismo, hay entornos de hardware (generalmente con unidades en red y posibles rutas múltiples al mismo encabezado) donde no funcionará correctamente.#pragma once
asumido, ¿cuál es la forma de contrarrestar ese incumplimiento?#pragma many
? ¿Cuántos compiladores han implementado algo así?Respuestas:
Aquí hay varias preguntas relacionadas:
¿Por qué
#pragma once
no se aplica automáticamente?Porque hay situaciones en las que desea incluir archivos más de una vez.
¿Por qué querría incluir un archivo varias veces?
Se han dado varias razones en otras respuestas (Boost.Preprocessor, X-Macros, incluidos los archivos de datos). Me gustaría agregar un ejemplo particular de "evitar la duplicación de código": OpenFOAM fomenta un estilo en el que
#include
ing bits y piezas dentro de las funciones es un concepto común. Vea, por ejemplo, esta discusión.Bien, pero ¿por qué no es el predeterminado con una opción de exclusión?
Porque en realidad no está especificado por el estándar.
#pragma
s son, por definición, extensiones específicas de la implementación.¿Por qué
#pragma once
no se ha convertido todavía en una función estandarizada (ya que cuenta con un amplio soporte)?Porque precisar qué es "el mismo archivo" de una manera independiente de la plataforma es sorprendentemente difícil. Consulte esta respuesta para obtener más información .
fuente
once
pragma once
falla pero incluir guardias hubiera funcionado. La identificación de archivos por ubicación tampoco funciona porque a veces el mismo archivo ocurre varias veces en un proyecto (por ejemplo, tiene 2 submódulos que incluyen una biblioteca solo de encabezado en sus encabezados y extraen su propia copia)#pragma STDC
familia . Pero todos controlan el comportamiento definido por la implementación.#ifndef XX
, no debe saber si hay algo después del correspondiente#endif
hasta que lea el archivo completo. Un compilador que realiza un seguimiento de si el más externo#ifndef
incluye todo el archivo y observa qué macro comprueba puede evitar volver a escanear el archivo, pero una directiva para decir que no hay nada de importancia después del punto actual parecería mejor que confiar en los compiladores para recuerda esas cosas.Puede usar en
#include
cualquier lugar de un archivo, no solo en el ámbito global, como dentro de una función (y varias veces si es necesario). Seguro, feo y no de buen estilo, pero posible y ocasionalmente sensato (dependiendo del archivo que incluyas). Si#include
solo fuera algo de una sola vez, entonces eso no funcionaría.#include
simplemente hace una sustitución de texto tonta (cortar y pegar) después de todo, y no todo lo que incluye tiene que ser un archivo de encabezado. Puede, por ejemplo,#include
un archivo que contenga datos generados automáticamente que contengan los datos sin procesar para inicializar unstd::vector
. Me gustastd::vector<int> data = { #include "my_generated_data.txt" }
Y que "my_generated_data.txt" sea algo generado por el sistema de compilación durante la compilación.
O tal vez soy vago / tonto / estúpido y pongo esto en un archivo ( ejemplo muy elaborado):
const noexcept;
y luego lo hago
class foo { void f1() #include "stupid.file" int f2(int) #include "stupid.file" };
Otro ejemplo, un poco menos elaborado, sería un archivo de origen en el que muchas funciones necesitan usar una gran cantidad de tipos en un espacio de nombres, pero no quiere decir simplemente
using namespace foo;
globalmente, ya que eso contaminaría el espacio de nombres global con muchas otras cosas. no quieres. Así que crea un archivo "foo" que contieneusing std::vector; using std::array; using std::rotate; ... You get the idea ...
Y luego haces esto en tu archivo fuente
void f1() { #include "foo" // needs "stuff" } void f2() { // Doesn't need "stuff" } void f3() { #include "foo" // also needs "stuff" }
Nota: No estoy abogando por hacer cosas como esta. Pero es posible y se hace en algunas bases de código y no veo por qué no debería estar permitido. Que no tienen sus usos.
También podría ser que el archivo que incluye se comporte de manera diferente según el valor de ciertas macros
#define
. Por lo tanto, es posible que desee incluir el archivo en varias ubicaciones, después de haber cambiado primero algún valor, para obtener un comportamiento diferente en diferentes partes de su archivo fuente.fuente
#define
s antes de cada inclusión que cambia el comportamiento del archivo incluido, es muy posible que deba incluirlo varias veces para obtener esos comportamientos diferentes en diferentes partes de mi archivo fuente.Incluir varias veces se puede utilizar, por ejemplo, con la técnica X-macro :
data.inc:
X(ONE) X(TWO) X(THREE)
use_data_inc_twice.c
enum data_e { #define X(V) V, #include "data.inc" #undef X }; char const* data_e__strings[]={ #define X(V) [V]=#V, #include "data.inc" #undef X };
No sé de ningún otro uso.
fuente
#pragma once
comportamiento obligatorio sería un cambio radical.Parece estar operando bajo el supuesto de que el propósito de la función "#include" incluso existente en el lenguaje es proporcionar soporte para la descomposición de programas en múltiples unidades de compilación. Eso es incorrecto.
Puede desempeñar ese papel, pero ese no era su propósito previsto. C se desarrolló originalmente como un lenguaje de nivel ligeramente superior que PDP-11 Macro-11 Assembly para reimplementar Unix. Tenía un preprocesador de macros porque era una característica de Macro-11. Tenía la capacidad de incluir textualmente macros de otro archivo porque esa era una característica de Macro-11 que el Unix existente que estaban portando a su nuevo compilador de C había hecho uso.
Ahora resulta que "#include" es útil para separar el código en unidades de compilación, como (posiblemente) un truco. Sin embargo, el hecho de que existiera este truco significó que se convirtió en el Camino que se hace en C. El hecho de que existiera un camino significaba que no era necesario crear ningún método nuevo para proporcionar esta funcionalidad, por lo que nada más seguro (por ejemplo: no vulnerable a múltiples -inclusión) fue creado. Como ya estaba en C, se copió en C ++ junto con la mayor parte del resto de la sintaxis y modismos de C.
Hay una propuesta para darle a C ++ un sistema de módulos adecuado para que finalmente se pueda prescindir de este truco de preprocesador de 45 años. Sin embargo, no sé qué tan inminente es esto. He escuchado que está en proceso durante más de una década.
fuente
No, esto obstaculizaría significativamente las opciones disponibles para, por ejemplo, los redactores de bibliotecas. Por ejemplo, Boost.Preprocessor permite usar bucles de preprocesador, y la única forma de lograrlos es mediante múltiples inclusiones del mismo archivo.
Y Boost.Preprocessor es un componente básico para muchas bibliotecas muy útiles.
fuente
#pragma reentrant
o algo por el estilo. La retrospectiva es 20/20.once
oreentrant
, para mitigar este u otros problemas potenciales.En el firmware del producto en el que trabajo principalmente, necesitamos poder especificar dónde se deben asignar las funciones y las variables globales / estáticas en la memoria. El procesamiento en tiempo real debe residir en la memoria L1 en el chip para que el procesador pueda acceder a él directamente y rápidamente. El procesamiento menos importante puede ir en la memoria L2 en el chip. Y cualquier cosa que no necesite ser manejada con especial rapidez puede vivir en el DDR externo y pasar por el almacenamiento en caché, porque no importa si es un poco más lento.
El #pragma para asignar a dónde van las cosas es una línea larga y no trivial. Sería fácil equivocarse. El efecto de equivocarse sería que el código / datos se colocarían silenciosamente en la memoria predeterminada (DDR), y el efecto de eso podría ser que el control de ciclo cerrado deje de funcionar sin ninguna razón que sea fácil de ver.
Entonces uso archivos de inclusión, que contienen solo ese pragma. Mi código ahora se ve así.
Archivo de cabecera...
#ifndef HEADERFILE_H #define HEADERFILE_H #include "set_fast_storage.h" /* Declare variables */ #include "set_slow_storage.h" /* Declare functions for initialisation on startup */ #include "set_fast_storage.h" /* Declare functions for real-time processing */ #include "set_storage_default.h" #endif
Y fuente ...
#include "headerfile.h" #include "set_fast_storage.h" /* Define variables */ #include "set_slow_storage.h" /* Define functions for initialisation on startup */ #include "set_fast_storage.h" /* Define functions for real-time processing */
Notará múltiples inclusiones del mismo archivo allí, incluso solo en el encabezado. Si escribo mal algo ahora, el compilador me dirá que no puede encontrar el archivo de inclusión "set_fat_storage.h" y que puedo arreglarlo fácilmente.
Entonces, en respuesta a su pregunta, este es un ejemplo real y práctico de dónde se requiere la inclusión múltiple.
fuente
_Pragma
directiva. Los mismos pragmas ahora se pueden expandir a partir de macros normales. Entonces no es necesario incluir más de una vez.