Je nettoie les inclus dans un projet C ++ sur lequel je travaille et je me demande si je devrais ou non inclure explicitement tous les en-têtes utilisés directement dans un fichier particulier, ou si je devrais inclure uniquement le strict minimum.
Voici un exemple Entity.hpp
:
#include "RenderObject.hpp"
#include "Texture.hpp"
struct Entity {
Texture texture;
RenderObject render();
}
(Supposons qu'une déclaration avant pour RenderObject
n'est pas une option.)
Maintenant, je sais que cela RenderObject.hpp
inclut Texture.hpp
- je le sais parce que chacun RenderObject
a un Texture
membre. Néanmoins, j’ai explicitement inclus Texture.hpp
dans Entity.hpp
, car je ne sais pas si c’est une bonne idée de s’en remettre à cela RenderObject.hpp
.
Donc: est-ce une bonne pratique ou pas?
#ifndef _RENDER_H #define _RENDER_H ... #endif
.#pragma once
règle-le, non?Réponses:
Vous devez toujours inclure tous les en-têtes définissant les objets utilisés dans un fichier .cpp dans ce fichier, indépendamment de ce que vous savez sur le contenu de ces fichiers. Vous devez inclure des gardes dans tous les fichiers d'en-tête pour vous assurer que l'inclusion d'en-têtes plusieurs fois n'a pas d'importance.
Les raisons:
Texture
objets de ce fichier.RenderObject.hpp
pas besoin de vous-Texture.hpp
même.Un corollaire est que vous ne devriez jamais inclure un en-tête dans un autre en-tête, sauf si cela est explicitement requis dans ce fichier.
la source
La règle générale est la suivante: incluez ce que vous utilisez. Si vous utilisez directement un objet, incluez directement son fichier d'en-tête. Si vous utilisez un objet A qui utilise B mais n'utilisez pas B vous-même, incluez seulement Ah
De plus, pendant que nous sommes sur le sujet, vous ne devriez inclure d'autres fichiers d'en-tête dans votre fichier d'en-tête que si vous en avez réellement besoin dans l'en-tête. Si vous n'en avez besoin que dans le fichier .cpp, incluez-le uniquement ici. C'est la différence entre une dépendance publique et une dépendance privée. Cela empêchera les utilisateurs de votre classe d'insérer des en-têtes dont ils n'ont pas vraiment besoin.
la source
Oui.
Vous ne savez jamais quand ces autres en-têtes pourraient changer. Il est logique d’inclure dans chaque unité de traduction les en-têtes dont vous savez qu’elle a besoin.
Nous avons des gardes en-tête pour nous assurer que la double inclusion n'est pas préjudiciable.
la source
Les opinions diffèrent sur ce point, mais je suis d’avis que chaque fichier (qu’il s’agisse d’un fichier source c / cpp ou d’un fichier d’en-tête h / hpp) devrait pouvoir être compilé ou analysé seul.
En tant que tels, tous les fichiers doivent # inclure tous les fichiers d’en-tête dont ils ont besoin - vous ne devez pas supposer qu’un seul fichier d’en-tête a déjà été inclus auparavant.
C'est vraiment pénible de devoir ajouter un fichier d'en-tête et de constater qu'il utilise un élément défini ailleurs, sans l'inclure directement ... vous devez donc aller chercher (et éventuellement vous retrouver avec le mauvais!)
De l’autre côté, peu importe (en règle générale) si vous incluez un fichier dont vous n’avez pas besoin ...
En tant que style personnel, j’organise les fichiers #include dans l’ordre alphabétique, en système et en application, ce qui renforce le message "autonome et entièrement cohérent".
la source
Cela dépend si l'inclusion transitive est par nécessité (par exemple, classe de base) ou en raison d'un détail d'implémentation (membre privé).
Pour clarifier, l'inclusion transitive est nécessaire lorsque la suppression est possible uniquement après avoir modifié les interfaces déclarées dans l'en-tête intermédiaire. Comme c'est déjà un changement radical, tout fichier .cpp qui l'utilise doit quand même être vérifié.
Exemple: Ah est inclus par Bh qui est utilisé par C.cpp. Si Bh a utilisé Ah pour certains détails d'implémentation, C.cpp ne devrait pas présumer que Bh continuera à le faire. Mais si Bh utilise Ah pour une classe de base, C.cpp peut alors supposer que Bh continuera d'inclure les en-têtes appropriés pour ses classes de base.
Vous voyez ici l'avantage réel de ne PAS dupliquer les inclusions d'en-tête. Dites que la classe de base utilisée par Bh n’appartient vraiment pas à Ah et qu’elle est refactorisée dans Bh même. Bh est maintenant un en-tête autonome. Si C.cpp a redondé Ah, il inclut maintenant un en-tête inutile.
la source
Il peut y avoir un autre cas: vous avez Ah, Bh et votre C.cpp, Bh comprend Ah
donc dans C.cpp, vous pouvez écrire
Donc, si vous n'écrivez pas #include "Ah" ici, que peut-il se passer? dans votre C.cpp, A et B (par exemple, classe) sont utilisés. Plus tard, vous avez changé votre code C.cpp, supprimez les éléments liés à B, mais en laissant Bh inclus.
Si vous incluez à la fois Ah et Bh, les outils permettant de détecter les inclusions inutiles peuvent vous aider à indiquer que l'inclusion de Bh n'est plus nécessaire. Si vous n'incluez que Bh comme ci-dessus, il est difficile pour les outils / humains de détecter l'inclusion inutile après le changement de code.
la source
Je prends une approche similaire légèrement différente des réponses proposées.
Dans les en-têtes, incluez toujours juste un minimum, juste ce qui est nécessaire pour que la compilation passe. Utilisez la déclaration anticipée chaque fois que possible.
Dans les fichiers source, peu importe combien vous incluez. Mes préférences sont toujours d'inclure le minimum pour le faire passer.
Pour les petits projets, inclure les en-têtes ici et là ne fera aucune différence. Mais pour les projets de moyenne à grande envergure, cela peut devenir un problème. Même si le matériel le plus récent est utilisé pour la compilation, la différence peut être perceptible. La raison en est que le compilateur doit toujours ouvrir l'en-tête inclus et l'analyser. Donc, pour optimiser la construction, appliquez la technique ci-dessus (incluez le strict minimum et utilisez forward, déclarez).
Bien que légèrement obsolète, la conception logicielle C ++ à grande échelle (par John Lakos) explique tout cela en détail.
la source
Une bonne pratique consiste à ne pas s'inquiéter de votre stratégie d'en-tête tant qu'elle est compilée.
La section d'en-tête de votre code est juste un bloc de lignes que personne ne devrait même regarder jusqu'à ce que vous obteniez une erreur de compilation facile à résoudre. Je comprends le désir d'un style "correct", mais aucune des deux ne peut vraiment être décrite comme correcte. L'inclusion d'un en-tête pour chaque classe est plus susceptible de provoquer des erreurs de compilation gênantes basées sur les commandes, mais ces erreurs de compilation reflètent également des problèmes qu'un codage soigneux pourrait résoudre (même si on peut dire qu'ils ne valent pas la peine d'être résolus).
Et oui, vous aurez ces problèmes liés aux ordres une fois que vous commencerez à vous immerger
friend
.Vous pouvez penser au problème dans deux cas.
Cas 1: Vous avez un petit nombre de classes en interaction, disons moins d'une douzaine. Vous ajoutez régulièrement, supprimez et modifiez ces en-têtes de manière à affecter leurs dépendances les unes par rapport aux autres. C'est le cas suggéré par votre exemple de code.
L'ensemble des en-têtes est suffisamment petit pour qu'il ne soit pas compliqué de résoudre les problèmes éventuels. Tous les problèmes difficiles sont résolus en réécrivant un ou deux en-têtes. S'inquiéter de votre stratégie d'en-tête, c'est résoudre des problèmes qui n'existent pas.
Cas 2: Vous avez des dizaines de cours. Certaines classes représentent l’épine dorsale de votre programme, et réécrire leurs en-têtes vous obligerait à réécrire / recompiler une grande partie de votre base de code. D'autres classes utilisent cette colonne vertébrale pour accomplir des choses. Cela représente un contexte commercial typique. Les en-têtes sont répartis sur des répertoires et vous ne pouvez pas vous rappeler de manière réaliste les noms de tout.
Solution: À ce stade, vous devez penser à vos classes dans des groupes logiques et regrouper ces groupes dans des en-têtes qui vous évitent d’être obligés de vous
#include
répéter. Cela ne simplifie pas la vie, mais constitue également une étape nécessaire pour tirer parti des en- têtes précompilés .Vous finissez par avoir des
#include
cours dont vous n'avez pas besoin, mais qui s'en soucie ?Dans ce cas, votre code ressemblerait à ...
la source