«Utilisation de l'espace de noms» dans les en-têtes C ++

119

Dans tous nos cours C ++, tous les professeurs mettent toujours using namespace std;juste après le #includes dans leurs .hfichiers. Cela me semble dangereux car en incluant cet en-tête dans un autre programme j'obtiendrai l'espace de noms importé dans mon programme, peut-être sans le réaliser, le vouloir ou le vouloir (l'inclusion d'en-tête peut être très profondément imbriquée).

Ma question est donc double: ai-je raison de using namespacene pas utiliser dans les fichiers d'en-tête, et / ou y a-t-il un moyen de l'annuler, quelque chose comme:

//header.h
using namespace std {
.
.
.
}

Une autre question dans le même sens: un fichier d'en-tête devrait-il contenir #includetous les en-têtes dont son .cppfichier correspondant a besoin, uniquement ceux qui sont nécessaires pour les définitions d'en-tête et laisser le .cppfichier #includele reste, ou aucun et déclarer tout ce dont il a besoin extern?
Le raisonnement derrière la question est le même que ci-dessus: je ne veux pas de surprises lors de l'inclusion de .hfichiers.

De plus, si j'ai raison, est-ce une erreur courante? Je veux dire dans la programmation du monde réel et dans les «vrais» projets là-bas.

Je vous remercie.

Baruch
la source
3
En remarque, si vous obtenez des collisions de noms dues à des using namespaceinstructions, vous pouvez utiliser le nom complet pour résoudre le problème.
Marius Bancila

Réponses:

115

Vous ne devriez certainement PAS utiliser using namespacedans les en-têtes précisément pour la raison que vous dites, que cela peut changer de manière inattendue la signification du code dans tous les autres fichiers qui incluent cet en-tête. Il n'y a aucun moyen d'annuler un using namespacequi est une autre raison pour laquelle c'est si dangereux. J'utilise généralement juste grepou similaire pour m'assurer que cela using namespacen'est pas appelé dans les en-têtes plutôt que d'essayer quelque chose de plus compliqué. Les vérificateurs de code statiques le signalent probablement aussi.

L'en-tête doit inclure uniquement les en-têtes dont il a besoin pour compiler. Un moyen simple de faire respecter cela est de toujours inclure le propre en-tête de chaque fichier source en premier lieu, avant les autres en-têtes. Ensuite, la compilation du fichier source échouera si l'en-tête n'est pas autonome. Dans certains cas, par exemple en faisant référence à des classes de détail d'implémentation dans une bibliothèque, vous pouvez utiliser des déclarations avant au lieu de #includeparce que vous avez un contrôle total sur la définition de cette classe déclarée avant.

Je ne suis pas sûr que je l'appellerais commun, mais cela apparaît définitivement de temps en temps, généralement écrit par de nouveaux programmeurs qui ne sont pas conscients des conséquences négatives. En règle générale, un peu d'éducation sur les risques permet de résoudre tous les problèmes, car ils sont relativement simples à résoudre.

Marque B
la source
2
sommes-nous libres d'utiliser des usingdéclarations dans nos .cppfichiers? les 3rdPartyLib::BigClassName<3rdPartyLib::AnotherBigName,3rdPartyLib::AnotherBigName>::Iterators sont la mort au bout des doigts.
Christopher
1
et comment rationaliser les templatefonctions - qui sont censées être dans les en-têtes? typedefs?
Christopher
1
@donlan, il semble que vous n'ayez obtenu aucune réponse pendant un certain temps ... Oui, vous pouvez utiliser des usinginstructions dans des .cppfichiers sans trop de soucis car la portée sera limitée à ce fichier, mais ne le faites jamais avant une #includeinstruction. En ce qui concerne les fonctions de modèle définies dans les en-têtes, malheureusement, je ne connais pas de bonne solution autre que la simple écriture de l'espace de noms ... Vous pourriez peut-être mettre unusing déclaration dans une portée séparée { /* using statement in between brackets */ }, cela l'empêcherait au moins de s'échapper du fichier actuel .
tjwrona1992
26

Point 59 dans Sutter et Alexandrescu «Normes de codage C ++: 101 règles, directives et meilleures pratiques» :

59. N'écrivez pas les utilisations de l'espace de noms dans un fichier d'en-tête ou avant un #include.

Les espaces de noms usingsont pour votre commodité, pas pour vous d'infliger aux autres: n'écrivez jamais une usingdéclaration ou une usingdirective avant une #includedirective.

Corollaire: dans les fichiers d'en-tête, n'écrivez pas de usingdirectives ou de usingdéclarations au niveau de l'espace de noms ; à la place, qualifiez explicitement tous les noms.

Un fichier d'en-tête est un invité dans un ou plusieurs fichiers source. Un fichier d'en-tête qui comprend des usingdirectives et des déclarations apporte aussi ses copains tapageurs.

Une using déclaration amène un copain. Une using directive fait entrer tous les copains dans l'espace de noms. L'utilisation par vos professeurs de using namespace std;est une directive d'utilisation.

Plus sérieusement, nous avons des espaces de noms pour éviter les conflits de noms. Un fichier d'en-tête est destiné à fournir une interface. La plupart des en-têtes ne savent pas quel code peut les inclure, maintenant ou dans le futur. L'ajout d' usinginstructions pour des raisons de commodité interne dans l'en-tête applique ces noms pratiques à tous les clients potentiels de cet en-tête. Cela peut conduire à des conflits de noms. Et c'est tout simplement impoli.

Andy Thomas
la source
12

Vous devez être prudent lorsque vous ajoutez des en-têtes à l'intérieur des en-têtes. Dans les grands projets, il peut créer une chaîne de dépendances très enchevêtrée qui déclenche des reconstructions plus importantes / plus longues que ce qui était réellement nécessaire. Consultez cet article et son suivi pour en savoir plus sur l'importance d'une bonne structure physique dans les projets C ++.

Vous ne devez inclure les en-têtes dans un en-tête que lorsque cela est absolument nécessaire (chaque fois que la définition complète d'une classe est nécessaire), et utiliser la déclaration directe partout où vous le pouvez (lorsque la classe est requise est un pointeur ou une référence).

En ce qui concerne les espaces de noms, j'ai tendance à utiliser la portée de l'espace de noms explicite dans mes fichiers d'en-tête et à ne mettre un using namespacedans mes fichiers cpp.

Mike O'Connor
la source
1
comment rationaliser la templatedéclaration de fonction? cela doit se produire dans l'en-tête, non?
Christopher
6

Consultez les normes de codage du Goddard Space Flight Center (pour C et C ++). Cela s'avère être un peu plus difficile qu'auparavant - voir les réponses mises à jour aux questions SO:

La norme de codage GSFC C ++ dit:

§3.3.7 Chaque fichier d'en-tête doit #includeles fichiers dont il a besoin pour compiler, plutôt que de forcer les utilisateurs aux #includefichiers nécessaires. #includesdoit être limité à ce dont l'en-tête a besoin; les autres #includesdoivent être placés dans le fichier source.

La première des questions croisées comprend maintenant une citation de la norme de codage GSFC C et la justification, mais la substance finit par être la même.

Jonathan Leffler
la source
5

Vous avez raison de dire que l' using namespaceen-tête est dangereux. Je ne sais pas comment le défaire. Il est facile de le détecter mais il suffit de rechercherusing namespace dans les fichiers en-tête. Pour cette dernière raison, il est rare dans les projets réels. Des collègues plus expérimentés se plaindront bientôt si quelqu'un fait quelque chose comme ça.

Dans de vrais projets, les gens essaient de minimiser la quantité de fichiers inclus, car moins vous en incluez, plus la compilation est rapide. Cela fait gagner du temps à tout le monde. Cependant, si le fichier d'en-tête suppose que quelque chose doit être inclus avant lui, il doit l'inclure lui-même. Sinon, cela rend les en-têtes non autonomes.

Öö Tiib
la source
4

Vous avez raison. Et tout fichier ne doit inclure que les en-têtes nécessaires à ce fichier. Quant à "faire les choses mal est-il courant dans les projets du monde réel?" - Oh oui!


la source
4

Comme toute chose en programmation, le pragmatisme devrait l'emporter sur le dogmatisme, l'OMI.

Tant que vous prenez la décision à l'échelle du projet ("Notre projet utilise largement STL, et nous ne voulons pas avoir à tout ajouter avec std ::."), Je ne vois pas le problème avec cela. La seule chose que vous risquez, ce sont les collisions de noms, après tout, et avec l'omniprésence de la STL, il est peu probable que ce soit un problème.

D'un autre côté, s'il s'agissait d'une décision d'un développeur dans un seul fichier d'en-tête (non privé), je peux voir comment cela générerait de la confusion au sein de l'équipe et devrait être évité.

ijprest
la source
4

En ce qui concerne "Y a-t-il un moyen d'annuler [a using déclaration]?"

Je pense qu'il est utile de souligner que les usingdéclarations sont affectées par la portée.

#include <vector>

{   // begin a new scope with {
    using namespace std;
    vector myVector;  // std::vector is used
}   // end the scope with }

vector myOtherVector;   // error vector undefined
std::vector mySTDVector // no error std::vector is fully qualified

Donc effectivement oui. En limitant la portée de la usingdéclaration, son effet ne dure que dans cette portée; il est «annulé» lorsque cette portée se termine.

Lorsque la usingdéclaration est déclarée dans un fichier en dehors de toute autre portée, elle a une portée de fichier et affecte tout ce qui se trouve dans ce fichier.

Dans le cas d'un fichier d'en-tête, si la usingdéclaration est à portée de fichier, cela s'étendra à la portée de tout fichier dans lequel l'en-tête est inclus.

JeuneJohn
la source
2
vous semblez être le seul à avoir compris la question réelle ... cependant, ma compilation n'est pas très heureuse que j'utilise à l'intérieur de la décélération de classe.
rustypaper
Cette réponse pourrait être encore meilleure en expliquant le problème avec l'idée du PO sur la façon dont la portée devrait fonctionner (comme la namespacedéclaration) par rapport à la façon dont elle fonctionne réellement (comme une variable). {}l'enfermant limiter sa portée, {}après qu'il ne fasse rien à ce sujet. C'est une manière accidentelle que le using namespaceest appliqué globalement.
TafT
2

Je pense que vous pouvez utiliser `` using '' dans les en-têtes C ++ en toute sécurité si vous écrivez vos déclarations dans un espace de noms imbriqué comme celui-ci:

namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED
{
    /*using statements*/

    namespace DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED
    {
        /*declarations*/
    }
}

using namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED::DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED;

Cela ne doit inclure que les éléments déclarés dans 'DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED' sans les espaces de noms utilisés. Je l'ai testé sur le compilateur mingw64.

AnArrayOfFunctions
la source
C'est une technique utile que je n'avais jamais vue auparavant; Merci. Normalement, je suis d'accord avec l'utilisation de la qualification complète et de placer les usingdéclarations dans les définitions de fonction où je peux afin qu'elles ne polluent pas les espaces de noms en dehors de la fonction. Mais maintenant, je souhaite utiliser des littéraux définis par l'utilisateur C ++ 11 dans un fichier d'en-tête, et selon la convention habituelle, les opérateurs littéraux sont protégés par un espace de noms; mais je ne veux pas les utiliser dans des listes d'initialiseurs de constructeurs qui ne sont pas dans une portée où je peux utiliser une usingdéclaration non polluante . C'est donc idéal pour résoudre ce problème.
Anthony Hall
Bien qu'un effet secondaire malheureux de ce modèle est que toutes les classes déclarées dans l'espace de noms plus à l' intérieur sera affiché dans les messages d'erreur du compilateur avec le nom complet: error: ... DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED:: DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED::ClassName .... Au moins, c'est ce qui se passe pour moi dans g ++.
Anthony Hall