web-dev-qa-db-fra.com

Pourquoi n'est-il pas une fois automatiquement supposé #pragma?

Quel est l'intérêt de dire spécifiquement au compilateur d'inclure le fichier une seule fois? Cela n'aurait-il pas un sens par défaut? Y a-t-il même une raison d'inclure un seul fichier plusieurs fois? Pourquoi ne pas l'assumer? Est-ce à voir avec du matériel spécifique?

75
Johnny Cache

Il y a plusieurs questions connexes ici:

  • Pourquoi #pragma once n'est-il pas automatiquement appliqué?
    Parce qu'il existe des situations dans lesquelles vous souhaitez inclure des fichiers plusieurs fois.

  • Pourquoi voudriez-vous inclure un fichier plusieurs fois?
    Plusieurs raisons ont été données dans d’autres réponses (Boost.Preprocessor, X-Macros, y compris les fichiers de données). J'aimerais ajouter un exemple particulier "d'éviter la duplication de code": OpenFOAM encourage un style dans lequel #includeing des éléments de fonctions est un concept commun. Voir par exemple this discussion.

  • Ok, mais pourquoi n'est-ce pas la valeur par défaut avec opt-out?
    Parce que ce n'est pas spécifié par la norme. #pragmas sont par définition des extensions spécifiques à l'implémentation.

  • Pourquoi #pragma once n'est-il pas encore devenu une fonctionnalité normalisée (car elle est largement prise en charge)?
    Parce qu'épingler ce qui est "le même fichier" d'une manière indépendante de la plate-forme est en fait étonnamment difficile. Voir cette réponse pour plus d'informations .

80
Max Langhof

Vous pouvez utiliser #includen'importe où dans un fichier, pas seulement à l'échelle globale - comme dans une fonction (et plusieurs fois si nécessaire). Bien sûr, moche et pas bon style, mais possible et parfois raisonnable (selon le fichier que vous incluez). Si #include n'était qu'une chose à la fois, cela ne fonctionnerait pas. #include ne fait que substituer du texte stupide (couper-coller), et tout ce que vous incluez ne doit pas nécessairement être un fichier d'en-tête. Vous pouvez, par exemple, #include un fichier contenant des données générées automatiquement contenant les données brutes pour initialiser un std::vector. Comme

std::vector<int> data = {
#include "my_generated_data.txt"
}

Et que "my_generated_data.txt" soit généré par le système de compilation lors de la compilation.

Ou peut-être que je suis paresseux/idiot/stupide et que je mets ceci dans un fichier (très exemple artificiel):

const noexcept;

et puis je fais

class foo {
    void f1()
    #include "stupid.file"
    int f2(int)
    #include "stupid.file"
};

Un autre exemple, un peu moins élaboré, serait un fichier source dans lequel de nombreuses fonctions doivent utiliser un grand nombre de types dans un espace de noms, mais vous ne voulez pas simplement dire using namespace foo; globalement, car cela modifierait l'espace de noms global avec beaucoup d'autres choses que vous ne voulez pas. Donc, vous créez un fichier "foo" contenant

using std::vector;
using std::array;
using std::rotate;
... You get the idea ...

Et puis vous faites cela dans votre fichier source

void f1() {
#include "foo" // needs "stuff"
}

void f2() {
    // Doesn't need "stuff"
}

void f3() {
#include "foo" // also needs "stuff"
}

Note: Je ne préconise pas de faire des choses comme ça. Mais cela est possible et fait dans certaines bases de code et je ne vois pas pourquoi cela ne devrait pas être autorisé. Il a a ses utilisations.

Il se peut également que le fichier que vous incluez se comporte différemment en fonction de la valeur de certaines macros (#defines). Ainsi, vous souhaiterez peut-être inclure le fichier à plusieurs emplacements, après avoir d'abord modifié une valeur, afin d'obtenir un comportement différent dans différentes parties de votre fichier source.

36
Jesper Juhl

Inclure plusieurs fois est utilisable, par exemple, avec la technique 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
};

Je ne connais aucune autre utilisation.

26
PSkocik

Vous semblez supposer que l'objectif de la fonction "#include", même dans le langage, est de prendre en charge la décomposition de programmes en plusieurs unités de compilation. C'est incorrect.

Il peut jouer ce rôle, mais ce n’était pas le but recherché. C était développé à l'origine un langage légèrement plus développé que PDP-11 Macro-11 Assembly pour la réimplémentation d'Unix. Il y avait un préprocesseur de macro parce que c'était une fonctionnalité de Macro-11. Il avait la possibilité d'inclure textuellement des macros provenant d'un autre fichier car il s'agissait d'une fonctionnalité de Macro-11 que les Unix existants qu'ils portaient sur leur nouveau compilateur C avaient utilisé.

Maintenant, il s'avère que "#include" est utile pour séparer le code en unités de compilation, comme (de manière discutable) un peu un bidouillage. Cependant, le fait que ce hack existe signifie qu'il est devenu The Way qui est fait en C. Le fait qu'il existe un chemin signifie qu'aucune nouvelle méthode n'a jamais été créée pour fournir cette fonctionnalité, donc rien de plus sûr (par exemple: non vulnérable à l'inclusion multiple) n'a jamais été créé. Comme il était déjà en C, il a été copié en C++ avec la plupart de la syntaxe et des idiomes du C.

Il y a une proposition pour donner à C++ n système de module approprié donc ce piratage de pré-processeur de 45 ans peut enfin être supprimé. Je ne sais pas à quel point c'est imminent. J'en ai entendu parler depuis plus de 10 ans.

19
T.E.D.

Non, cela entraverait considérablement les options disponibles, par exemple, pour les rédacteurs de bibliothèques. Par exemple, Boost.Preprocessor permet d’utiliser des boucles de pré-traitement, et la seule façon de les réaliser consiste à utiliser plusieurs inclusions du même fichier.

Et Boost.Preprocessor est un bloc de construction pour de nombreuses bibliothèques très utiles.

10
SergeyA

Dans le micrologiciel du produit sur lequel je travaille principalement, nous devons pouvoir spécifier où les fonctions et les variables globales/statiques doivent être allouées en mémoire. Le traitement en temps réel doit vivre dans la mémoire L1 sur puce afin que le processeur puisse y accéder directement et rapidement. Un traitement moins important peut aller dans la mémoire L2 sur puce. Et tout ce qui n’a pas besoin d’être traité de manière particulièrement rapide peut vivre dans le DDR externe et être mis en cache, car peu importe si c’est un peu plus lent.

Le pragma à allouer où les choses vont est une longue ligne non triviale. Il serait facile de se tromper. En se trompant, le code/les données seraient mis silencieusement dans la mémoire par défaut (DDR), et l’effet de que pourrait être un contrôle en boucle fermée arrêtant de fonctionner sans raison facile à utiliser. voir.

J'utilise donc des fichiers d'inclusion, qui ne contiennent que ce pragma. Mon code ressemble maintenant à ceci.

En tête de fichier...

#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

Et la source ...

#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 */

Vous remarquerez plusieurs inclusions du même fichier, même dans l'en-tête. Si je me trompe maintenant, le compilateur me dira qu'il ne trouve pas le fichier d'inclusion "set_fat_storage.h" et que je peux facilement le réparer.

Donc, pour répondre à votre question, il s'agit d'un exemple réel et pratique de la nécessité d'une inclusion multiple.

8
Graham