c++ - once - ¿Por qué no se asume automáticamente#pragma una vez?
#pragma c++ (6)
¿De qué sirve decirle al compilador específicamente que incluya el archivo solo una vez? ¿No tendría sentido por defecto? ¿Hay alguna razón para incluir un solo archivo varias veces? ¿Por qué no simplemente asumirlo? ¿Tiene que ver con hardware específico?
En el firmware del producto en el que trabajo principalmente, debemos poder especificar dónde se deben asignar las funciones y las variables globales / estáticas en la memoria. El procesamiento en tiempo real necesita vivir 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 del chip. Y cualquier cosa que no necesite ser manejada de manera particularmente rápida 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 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 / los datos se pondrían silenciosamente en la memoria predeterminada (DDR), y el efecto de eso podría ser el control de bucle cerrado que deja de funcionar sin ningún motivo que sea fácil de ver.
Así que uso los 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 me equivoco al escribir algo ahora, el compilador me dirá que no puede encontrar el archivo de inclusión "set_fat_storage.h" y puedo arreglarlo fácilmente.
Entonces, en respuesta a su pregunta, este es un ejemplo real y práctico de donde se requiere inclusión múltiple.
Hay varias preguntas relacionadas aquí:
-
¿Por qué no se aplica automáticamente
#pragma once
?
Porque hay situaciones en las que quieres incluir archivos más de una vez. -
¿Por qué querrías incluir un archivo varias veces?
Se han dado varias razones en otras respuestas (Boost.Preprocessor, X-Macros, incluyendo 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
los bits y piezas dentro de las funciones es un concepto común. Ver por ejemplo this discusión. -
Ok, pero ¿por qué no es el predeterminado con una opción de exclusión?
Porque no está realmente especificado por la norma.#pragma
s son, por definición, extensiones específicas de implementación. -
¿Por qué
#pragma once
no se ha convertido en una característica estandarizada (ya que es ampliamente compatible)?
Debido a que determinar qué es "el mismo archivo" de una manera independiente de la plataforma es realmente sorprendente. Vea esta respuesta para más información .
Incluirlo varias veces es utilizable, 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.
No, esto dificultaría significativamente las opciones disponibles para, por ejemplo, escritores de bibliotecas. Por ejemplo, Boost.Preprocessor permite utilizar bucles de preprocesador, y la única forma de lograrlos es mediante múltiples inclusiones del mismo archivo.
Y Boost.Preprocessor es un bloque de construcción para muchas bibliotecas muy útiles.
Parece que está operando bajo el supuesto de que el propósito de la característica "#include", incluso existente en el lenguaje, es brindar 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 fue su propósito. C se desarrolló originalmente como un lenguaje de nivel ligeramente superior al de PDP-11 Macro-11 Assembly para volver a implementar Unix. Tenía un preprocesador de macros porque esa 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 transfiriendo 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, ya que (posiblemente) un poco de pirateo. Sin embargo, el hecho de que existió este truco significó que se convirtió en el Camino que se realiza en C. El hecho de que existió un camino no significó que nunca fuera necesario crear un nuevo método para proporcionar esta funcionalidad, por lo que no hay nada más seguro (por ejemplo: no es vulnerable a múltiples -inclusión) fue creado siempre. Como ya estaba en C, se copió en C ++ junto con la mayor parte del resto de la sintaxis y los modismos de C.
Hay una propuesta para darle a C ++ un sistema de módulos adecuado para que este hack de preprocesador de 45 años finalmente pueda ser eliminado. Aunque no sé cuán inminente es esto. Hace más de una década que oigo hablar de que está en las obras.
Puede usar
#include
en
cualquier parte
de un archivo, no solo en el ámbito global, como dentro de una función (y varias veces si es necesario).
Claro, feo y no es un buen estilo, pero es posible y ocasionalmente sensible (según el archivo que incluya).
Si
#include
fuera solo una vez, no funcionaría.
#include
simplemente hace una sustitución de texto simple (cut''n''paste) después de todo, y no todo lo que incluye tiene que ser un archivo de encabezado.
Podría, por ejemplo,
#include
un archivo que contenga datos generados automáticamente que contengan datos sin procesar para inicializar un
std::vector
.
Me gusta
std::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 sea perezoso / tonto / estúpido y coloque esto en un archivo (ejemplo muy artificial):
const noexcept;
y luego hago
class foo {
void f1()
#include "stupid.file"
int f2(int)
#include "stupid.file"
};
Otro ejemplo, un poco menos artificial, sería un archivo de origen donde muchas funciones necesitan usar una gran cantidad de tipos en un espacio de nombres, pero no quiere decir simplemente
using namespace foo;
a nivel mundial, ya que eso contaminaría el espacio de nombres global con muchas otras cosas que no desea.
Así que creas un archivo "foo" que contiene
using 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 está hecho en algunos códigos y no veo por qué no debería permitirse. Tiene sus usos.
También podría ser que el archivo que incluye se comporte de manera diferente dependiendo del valor de ciertas macros (
#define
s).
Por lo tanto, es posible que desee incluir el archivo en varias ubicaciones, después de haber cambiado algo de valor por primera vez, para que obtenga un comportamiento diferente en diferentes partes de su archivo de origen.