web-dev-qa-db-fra.com

L'utilisation de double include gardes en C ++

J'ai donc récemment eu une discussion au cours de laquelle je travaillais, au cours de laquelle je remettais en question l'utilisation d'un double inclure garde sur un seul garde. Ce que je veux dire par double garde est le suivant:

fichier d'en-tête, "header_a.hpp":

#ifndef __HEADER_A_HPP__
#define __HEADER_A_HPP__
...
...
#endif

Lorsque vous incluez le fichier d'en-tête n'importe où, dans un fichier d'en-tête ou un fichier source:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif

Je comprends maintenant que l'utilisation de la protection dans les fichiers d'en-tête a pour but d'empêcher l'inclusion multiple d'un fichier d'en-tête déjà défini, qui est commun et bien documenté. Si la macro est déjà définie, l'intégralité du fichier d'en-tête est considérée comme "vide" par le compilateur et la double inclusion est empêchée. Assez simple.

Le problème que je ne comprends pas consiste à utiliser #ifndef __HEADER_A_HPP__ Et #endif Autour de #include "header_a.hpp". Le collègue me dit que cela ajoute une deuxième couche de protection aux inclusions mais je ne vois pas en quoi cette deuxième couche est même utile si la première couche fait absolument le travail (ou le fait-elle?).

Le seul avantage que je peux avoir, c'est que cela empêche le lieur de se gêner pour trouver le fichier. Est-ce que cela a pour but d'améliorer le temps de compilation (ce qui n'a pas été mentionné comme un avantage), ou y a-t-il autre chose au travail ici que je ne vois pas?

71
sh3rifme

Je suis à peu près sûr que c’est une mauvaise pratique d’ajouter un autre garde, tel que:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif

Voici quelques raisons:

  1. Pour éviter la double inclusion, il suffit d'ajouter une protection d'inclusion habituelle à l'intérieur du fichier d'en-tête. Ça fait bien le travail. Une autre option consiste à placer la protection à la place de l’inclusion, ce qui gâche le code et réduit la lisibilité.

  2. Cela ajoute des dépendances inutiles. Si vous modifiez include garde dans le fichier d’en-tête, vous devez le modifier en all où l’en-tête est inclus.

  3. Ce n’est certainement pas l’opération la plus coûteuse en comparant l’ensemble du processus de compilation/couplage, elle peut donc difficilement réduire le temps de construction total.

  4. N'importe quel compilateur qui vaut la peine optimise déjà les inclus-gardes au niveau du fichier .

105
Edgar Rokjān

La raison pour laquelle inclure des gardes dans le fichier en-tête est d'empêcher que le contenu de l'en-tête soit extrait plus d'une fois dans une unité de traduction. C'est une pratique normale et bien établie.

La mise en place de redondants inclut des gardes dans un fichier source afin d'éviter d'avoir à ouvrir le fichier d'en-tête inclus, et de revenir à l'ancienne, ce qui pourrait considérablement accélérer la compilation. De nos jours, l’ouverture d’un fichier est beaucoup plus rapide qu’auparavant; De plus, les compilateurs sont assez intelligents pour se souvenir des fichiers qu'ils ont déjà vus, et comprennent l'idiome include guard, ils peuvent donc comprendre par eux-mêmes qu'ils n'ont pas besoin de rouvrir le fichier. C'est un peu agité, mais l'essentiel est que cette couche supplémentaire n'est plus nécessaire.

EDIT: un autre facteur est que la compilation de C++ est loin plus compliquée que la compilation de C, il faut donc loin plus, ce qui réduit le temps passé à ouvrir les fichiers d'inclusion. , partie moins importante du temps nécessaire pour compiler une unité de traduction.

49
Pete Becker

Le seul avantage que je peux avoir est que cela empêche le lieur de se gêner pour trouver le fichier.

L'éditeur de liens ne sera affecté d'aucune façon.

Cela pourrait empêcher le pré-processeur de chercher le fichier, mais si la protection est définie, cela signifie qu'il a déjà trouvé le fichier. Je soupçonne que si le temps de pré-traitement est réduit du tout, l’effet serait assez minime, sauf dans la monstruosité la plus pathologiquement incluse.

L'inconvénient est que si la protection est modifiée (par exemple en raison d'un conflit avec une autre protection), toutes les conditions avant les directives d'inclusion doivent être modifiées pour fonctionner. Et si quelque chose d'autre utilise la protection précédente, les conditions doivent être modifiées pour que la directive include fonctionne correctement.

P.S. __HEADER_A_HPP__ est un symbole réservé à l’implémentation; vous ne pouvez donc pas le définir. Utilisez un autre nom pour le garde.

22
eerorika

Les compilateurs plus anciens sur des plates-formes plus traditionnelles (mainframe) (nous parlons ici du milieu des années 2000) n'avaient pas l'habitude de voir l'optimisation décrite dans d'autres réponses, ce qui a permis de ralentir considérablement le temps de pré-traitement, ce qui oblige à relire les fichiers d'en-tête. qui ont déjà été inclus (en gardant à l’esprit dans un grand projet monolithique d’entreprise, vous allez inclure BEAUCOUP de fichiers d’en-tête). A titre d'exemple, j'ai vu des données indiquant une vitesse multipliée par 26 pour un fichier contenant 256 fichiers d'en-tête, chacun comprenant les mêmes 256 fichiers d'en-tête sur le compilateur VisualAge C++ 6 pour AIX (datant du milieu des années 2000). C’est un exemple assez extrême, mais ce genre d’accélération s’ajoute.

Cependant, tous les compilateurs modernes, même sur les plates-formes mainframe telles que AIX et Solaris, effectuent suffisamment d'optimisation pour l'inclusion d'en-tête afin que la différence de nos jours soit vraiment négligeable. Par conséquent, il n'y a aucune bonne raison de les avoir plus.

Cela explique toutefois pourquoi certaines entreprises conservent encore cette pratique, car relativement récemment (du moins en termes d’âge en code C/C++), cela valait encore la peine pour de très grands projets monolithiques.

17
Muzer

Bien que certaines personnes s’y opposent, en pratique, "#pragma once" fonctionne parfaitement et les principaux compilateurs (gcc/g ++, vc ++) le prennent en charge.

Ainsi, quelle que soit l'argumentation puriste répandue, cela fonctionne beaucoup mieux:

  1. Vite
  2. Pas de maintenance, pas de problème avec la non-inclusion mystérieuse parce que vous avez copié un ancien drapeau
  3. Une seule ligne avec une signification évidente par opposition aux lignes cryptées réparties dans un fichier

Donc, tout simplement:

#pragma once

au début du fichier, et c'est tout. Optimisé, maintenable et prêt à l'emploi.

8
Bert Bril