236 votes

Comment utiliser correctement les espaces de noms en C++ ?

Je viens d'un environnement Java, où l'on utilise des paquets et non des espaces de noms. J'ai l'habitude de placer les classes qui fonctionnent ensemble pour former un objet complet dans des paquets, puis de les réutiliser plus tard à partir de ce paquet. Mais maintenant, je travaille en C++.

Comment utiliser les espaces de noms en C++ ? Créez-vous un seul espace de noms pour l'ensemble de l'application, ou créez-vous des espaces de noms pour les principaux composants ? Dans ce cas, comment crée-t-on des objets à partir de classes situées dans d'autres espaces de noms ?

170voto

Mark Ingram Points 24995

Les espaces de nommage sont essentiellement des paquets. Ils peuvent être utilisés comme ceci :

namespace MyNamespace
{
  class MyClass
  {
  };
}

Puis en code :

MyNamespace::MyClass* pClass = new MyNamespace::MyClass();

Ou, si vous voulez toujours utiliser un espace de noms spécifique, vous pouvez faire ceci :

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Editar: Suivant ce que bernhardrusch Cela dit, j'ai tendance à ne pas utiliser du tout la syntaxe "using namespace x", je spécifie généralement explicitement l'espace de noms lors de l'instanciation de mes objets (c'est-à-dire le premier exemple que j'ai montré).

Et comme vous l'avez demandé en dessous de vous pouvez utiliser autant d'espaces de noms que vous le souhaitez.

27 votes

IMO, il est préférable de s'habituer à préfixer le nom de l'utilisateur. std aux symboles plutôt que d'utiliser l'espace de nom using du tout. Alors j'écris toujours std::cout o std::string parce que c'est comme ça que je les appelle maintenant. Je n'aurais jamais écrit cout .

5 votes

Bien que cela soit très vrai pour std J'ai personnellement trouvé cela beaucoup moins important lorsque vous avez affaire à de petites bibliothèques. Souvent, vous pouvez simplement utiliser using namespace FooBario; Il n'est pas nécessaire d'avoir recours à un système de classification, en particulier si vous utilisez un nombre considérable de types provenant d'une bibliothèque.

4 votes

@jkerian, je vois votre point de vue, mais je ne suis pas d'accord car les collisions de noms sont (à mon avis) plus susceptibles de provenir précisément de ces petites bibliothèques. La plupart des gens font attention à ne pas nommer les classes/fonctions de la même façon que celles de la STL. Cela dit, je suis d'accord pour dire que using namespace X; doivent être évités dans les fichiers d'en-tête si possible.

121voto

bernhardrusch Points 4445

Pour éviter de dire tout ce que Mark Ingram a déjà dit, une petite astuce pour utiliser les espaces de noms :

Évitez la directive "using namespace" dans les fichiers d'en-tête - cela ouvre l'espace de noms pour toutes les parties du programme qui importent ce fichier d'en-tête. Dans les fichiers d'implémentation (*.cpp), ce n'est normalement pas un gros problème - bien que je préfère utiliser la directive "using namespace" au niveau des fonctions.

Je pense que les espaces de noms sont surtout utilisés pour éviter les conflits de noms - pas nécessairement pour organiser la structure de votre code. J'organiserais les programmes C++ principalement avec les fichiers d'en-tête / la structure des fichiers.

Les espaces de noms sont parfois utilisés dans les grands projets C++ pour cacher les détails de mise en œuvre.

Remarque supplémentaire concernant la directive using : Certaines personnes préfèrent utiliser "using" uniquement pour les éléments uniques :

using std::cout;  
using std::endl;

2 votes

L'un des avantages de "l'utilisation de l'espace de noms" au niveau de la fonction, comme vous le suggérez, plutôt qu'au niveau du fichier .cpp ou du bloc d'espace de noms {} dans le .cpp, est que cela facilite grandement les constructions d'unités de compilation unique. "L'utilisation de l'espace de noms est transitive et s'applique à l'espace de noms A à travers les blocs discrets de l'espace de noms A {} dans la même unité, donc pour les constructions d'unités de compilation unique, vous finissez rapidement par tout utiliser si elles sont faites au niveau du fichier ou du bloc de l'espace de noms.

0 votes

using std::cout; est une déclaration d'utilisation

3 votes

Est-il possible d'utiliser plusieurs les noms d'un simple dans une seule déclaration ? Quelque chose comme using std::cout, std::endl; ou même, using std::cout, endl; .

80voto

paercebal Points 38526

Vincent Robert a raison dans son commentaire Comment utiliser correctement les espaces de noms en C++ ? .

Utilisation de l'espace de noms

Les espaces de noms sont utilisés à tout le moins pour éviter les collisions de noms. En Java, cette règle est appliquée par le biais de l'idiome "org.domain" (car on suppose qu'une personne n'utilisera pas autre chose que son propre nom de domaine).

En C++, vous pourriez donner un espace de nom à tout le code de votre module. Par exemple, pour un module MyModule.dll, vous pouvez donner à son code l'espace de nom MyModule. J'ai vu ailleurs quelqu'un utiliser MyCompany::MyProject::MyModule. Je suppose que c'est exagéré, mais dans l'ensemble, cela me semble correct.

Utiliser "utiliser"

Using doit être utilisé avec beaucoup de précautions car il importe effectivement un (ou tous) les symboles d'un espace de nom dans votre espace de nom actuel.

C'est mauvais de le faire dans un fichier d'en-tête parce que votre en-tête polluera toutes les sources qui l'incluent (cela me rappelle les macros...), et même dans un fichier source, mauvais style en dehors de la portée d'une fonction parce qu'il importera à portée globale les symboles de l'espace de nom.

La manière la plus sûre d'utiliser "utiliser" est d'importer des symboles sélectionnés :

void doSomething()
{
   using std::string ; // string is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   std::cout << a << std::endl ;
}

void doSomethingElse()
{
   using namespace std ; // everything from std is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   cout << a << endl ;
}

Vous verrez beaucoup de "using namespace std ;" dans les tutoriels ou les exemples de codes. La raison est de réduire le nombre de symboles pour rendre la lecture plus facile, pas parce que c'est une bonne idée.

"using namespace std ;" est déconseillé par Scott Meyers (je ne me rappelle pas exactement quel livre, mais je peux le trouver si nécessaire).

Composition de l'espace de nommage

Les espaces de nommage sont plus que des paquets. Un autre exemple peut être trouvé dans l'ouvrage de Bjarne Stroustrup intitulé "The C++ Programming Language".

Dans l'"édition spéciale", à 8.2.8 Composition de l'espace de nom Il décrit comment fusionner deux espaces de noms AAA et BBB en un autre appelé CCC. Ainsi, CCC devient un alias pour AAA et BBB :

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

namespace CCC
{
   using namespace AAA ;
   using namespace BBB ;
}

void doSomethingAgain()
{
   CCC::doSomething() ;
   CCC::doSomethingElse() ;
}

Vous pourriez même importer des symboles sélectionnés à partir de différents espaces de noms, pour construire votre propre interface d'espace de noms personnalisée. Je n'ai pas encore trouvé d'utilisation pratique de ceci, mais en théorie, c'est cool.

0 votes

Pourriez-vous préciser, s'il vous plaît "donner un espace de nom à tout le code dans votre module" ? Quelle est la bonne pratique pour encapsuler un module ? Par exemple, j'ai une classe de nombres complexes et des fonctions externes liées aux nombres complexes. Cette classe et ces deux fonctions devraient être dans un seul espace de nom ?

77voto

Éric Malenfant Points 10082

Je n'ai pas vu de mention à ce sujet dans les autres réponses, alors voici mes deux centimes canadiens :

En ce qui concerne l'utilisation des espaces de noms, une déclaration utile est l'alias d'espace de noms, qui vous permet de "renommer" un espace de noms, normalement pour lui donner un nom plus court. Par exemple, au lieu de :

Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;

vous pouvez écrire :

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;

56voto

Vincent Robert Points 16530

N'écoutez pas tous ceux qui vous disent que les espaces de noms ne sont que des espaces de noms.

Ils sont importants car ils sont considérés par le compilateur pour appliquer le principe d'interface. En gros, on peut l'expliquer par un exemple :

namespace ns {

class A
{
};

void print(A a)
{
}

}

Si vous vouliez imprimer un objet A, le code serait celui-ci :

ns::A a;
print(a);

Notez que nous n'avons pas mentionné explicitement l'espace de nom lors de l'appel de la fonction. C'est le principe de l'interface : Le C++ considère qu'une fonction prenant un type en argument fait partie de l'interface de ce type, il n'est donc pas nécessaire de spécifier l'espace de noms car le paramètre implique déjà l'espace de noms.

Maintenant, pourquoi ce principe est important ? Imaginez que l'auteur de la classe A n'a pas fourni de fonction print() pour cette classe. Vous devrez en fournir une vous-même. Comme vous êtes un bon programmeur, vous allez définir cette fonction dans votre propre espace de noms, ou peut-être dans l'espace de noms global.

namespace ns {

class A
{
};

}

void print(A a)
{
}

Et votre code peut commencer à appeler la fonction print(a) où vous voulez. Maintenant, imaginez que des années plus tard, l'auteur décide de fournir une fonction print(), meilleure que la vôtre parce qu'il connaît les internes de sa classe et peut faire une meilleure version que la vôtre.

Les auteurs de C++ ont alors décidé que sa version de la fonction print() devait être utilisée à la place de celle fournie dans un autre espace de noms, afin de respecter le principe d'interface. Et que cette "mise à niveau" de la fonction print() devait être aussi simple que possible, ce qui signifie que vous ne devrez pas modifier chaque appel à la fonction print(). C'est pourquoi les "fonctions d'interface" (fonction dans le même espace de noms qu'une classe) peuvent être appelées sans spécifier l'espace de noms en C++.

C'est pourquoi vous devez considérer un espace de noms C++ comme une "interface" lorsque vous en utilisez une et garder à l'esprit le principe d'interface.

Si vous voulez une meilleure explication de ce comportement, vous pouvez vous référer au livre Un C++ exceptionnel de Herb Sutter

25 votes

Vous devez effectivement modifier chaque appel à print() si ns::Print est ajouté, mais le compilateur signalera chaque appel comme ambigu. Passer silencieusement à la nouvelle fonction serait une mauvaise idée.

0 votes

Je me demande maintenant, ayant ce que @Vincent a dit que vous devrez changer tous les appels à print, si autor fournirait la fonction ns::Print(), ce que vous essayez de dire ? Que lorsque l'auteur a ajouté une fonction ns::Print(), vous pouvez simplement supprimer votre propre implémentation ? Ou que vous ajouterez simplement la déclaration d'utilisation de ns::print() ? Ou quelque chose d'autre ? Merci

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X