Comment utilisez-vous correctement les espaces de noms en C ++?

231

Je viens d'un arrière-plan Java, où les packages sont utilisés, pas des espaces de noms. J'ai l'habitude de mettre des classes qui fonctionnent ensemble pour former un objet complet dans des packages, puis de les réutiliser plus tard à partir de ce package. Mais maintenant je travaille en C ++.

Comment utilisez-vous les espaces de noms en C ++? Créez-vous un seul espace de noms pour toute l'application ou créez-vous des espaces de noms pour les principaux composants? Si oui, comment créez-vous des objets à partir de classes dans d'autres espaces de noms?

Marius
la source

Réponses:

167

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

namespace MyNamespace
{
  class MyClass
  {
  };
}

Puis en code:

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

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

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Edit: Suite à ce que bernhardrusch a dit, j'ai tendance à ne pas utiliser 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é ci - dessous , vous pouvez utiliser autant d'espaces de noms que vous le souhaitez.

Mark Ingram
la source
25
OMI, il vaut mieux s'habituer à préfixer l' stdespace de noms aux symboles plutôt qu'à utiliser usingdu tout. Donc j'écris toujours std::coutou std::stringmaintenant parce que c'est comme ça que je les appelle maintenant. Je n'écrirais jamais cout.
Tom Savage
5
Bien que cela soit très vrai pour std, j'ai personnellement trouvé cela beaucoup moins important lorsque vous traitez avec des bibliothèques plus petites. Souvent, vous pouvez simplement utiliser using namespace FooBario;, en particulier si vous utilisez un nombre considérable de types d'une bibliothèque.
jkerian
4
@jkerian, je vois votre point, mais je ne suis pas d'accord parce que les collisions de noms sont (dans mon esprit) 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 manière que celles de STL. Cela dit, je conviens que cela using namespace X;devrait être évité dans les fichiers d'en-tête si possible.
Alan Turing,
12
@LexFridman "La plupart des gens font attention à ne pas nommer les classes / fonctions de la même manière que celles de STL" - ce n'est donc PAS VRAI. Par exemple, si je devais écrire du code d'E / S très spécialisé pour un matériel étrange, je n'utiliserais jamais autre chose que mylibrary::endlpour représenter ma propre séquence spéciale de nouvelle ligne. Je veux dire, pourquoi inventer des noms?
Mon compilateur ne reconnaît toujours pas l'espace de noms, même si je veux le spécifier explicitement et que j'inclus le fichier où il est déclaré.
bgenchel
116

Pour éviter de tout dire, Mark Ingram a déjà dit un petit conseil 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 - même si je préfère utiliser la directive "using namespace" au niveau de la fonction.

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

Parfois, les espaces de noms sont utilisés dans des projets C ++ plus gros pour masquer les détails de l'implémentation.

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

using std::cout;  
using std::endl;
bernhardrusch
la source
2
Un avantage 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 au niveau du bloc namespace {} au sein du fichier .cpp est qu'il facilite grandement les constructions à unité de compilation unique. "Utiliser l'espace de noms" est transitif et s'applique à l'espace de noms A sur des blocs d'espace de noms A {} discrets dans la même unité, donc pour les versions à compilation unique, vous finissez rapidement par tout utiliser si elles sont effectuées au niveau du fichier ou du bloc d'espace de noms.
idij
using std::cout; est une déclaration d'utilisation
Konstantin
3
Est-il possible d'utiliser plusieurs noms d'un même espace de noms dans une seule instruction? Quelque chose comme using std::cout, std::endl;ou même using std::cout, endl;,.
AlQuemist
Il peut être correct d'utiliser un using namespace xdans un en-tête s'il se trouve dans un autre espace de noms. Ce n'est pas quelque chose que je recommanderais en général, mais cela ne pollue pas l'espace de noms global.
Praxeolitic
79

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

Utiliser l'espace de noms

Les espaces de noms sont utilisés à tout le moins pour éviter la collision de noms. En Java, cela est imposé par l'idiome "org.domain" (car il est supposé que l'on n'utilisera rien d'autre que son propre nom de domaine).

En C ++, vous pouvez donner un espace de noms à 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 "en utilisant"

L'utilisation doit être utilisée avec le plus grand soin car elle importe efficacement un (ou tous) symboles d'un espace de noms dans votre espace de noms actuel.

C'est mal de le faire dans un fichier d'en-tête car votre en-tête polluera toutes les sources y compris (cela me rappelle les macros ...), et même dans un fichier source, mauvais style en dehors d'une portée de fonction car il importera à portée globale les symboles de l'espace de noms.

La façon la plus sûre d'utiliser "à l'aide de" est d'importer certains symboles:

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 des tutoriels ou des exemples de codes. La raison est de réduire le nombre de symboles pour faciliter la lecture, non pas parce que c'est une bonne idée.

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

Composition de l'espace de noms

Les espaces de noms sont plus que des packages. Un autre exemple peut être trouvé dans "Le langage de programmation C ++" de Bjarne Stroustrup.

Dans la "Special Edition", au 8.2.8 Composition des espaces de noms , il décrit comment vous pouvez 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 pouvez même importer des symboles sélectionnés à partir de différents espaces de noms, pour créer votre propre interface d'espace de noms personnalisée. Je n'ai pas encore trouvé une utilisation pratique de cela, mais en théorie, c'est cool.

paercebal
la source
Pourriez-vous clarifier, veuillez "donner un espace de noms à tout le code de votre module"? Quelle est la bonne pratique pour encapsuler dans le 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 noms?
yanpas
74

Je n'en ai vu aucune mention dans les autres réponses, voici donc mes 2 cents canadiens:

Sur la rubrique "Utilisation de l'espace de noms", une déclaration utile est l'alias d'espace de noms, vous permettant 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;

tu peux écrire:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
Éric Malenfant
la source
55

N'écoutez pas toutes les personnes vous dire que les espaces de noms ne sont que des espaces de noms.

Ils sont importants car ils sont considérés par le compilateur comme appliquant le principe d'interface. Fondamentalement, cela peut être expliqué 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 noms lors de l'appel de la fonction. C'est le principe de l'interface: C ++ considère une fonction prenant un type comme argument comme faisant partie de l'interface pour ce type, donc pas besoin de spécifier l'espace de noms car le paramètre impliquait déjà l'espace de noms.

Maintenant, pourquoi ce principe est important? Imaginez que l'auteur de la classe A n'ait pas fourni de fonction print () pour cette classe. Vous devrez en fournir un vous-même. Comme vous êtes un bon programmeur, vous définirez 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 le souhaitez. Imaginez maintenant que des années plus tard, l'auteur décide de fournir une fonction print (), meilleure que la vôtre car il connaît les internes de sa classe et peut faire une meilleure version que la vôtre.

Les auteurs 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, pour respecter le principe d'interface. Et que cette "mise à niveau" de la fonction print () devrait être aussi simple que possible, ce qui signifie que vous n'aurez pas à changer 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 ++.

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

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

Vincent Robert
la source
23
Vous devez réellement changer chaque appel à print () si ns :: Print est ajouté, mais le compilateur marquera chaque appel comme ambigu. Passer en silence à la nouvelle fonction serait une idée terrible.
Eclipse
Je me demande maintenant, ayant ce que @Vincent a dit que vous devrez changer tous les appels à imprimer, si autor fournirait la fonction ns :: Print (), qu'essayiez-vous 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 en utilisant ns :: print () using-declaration? Ou autre chose? Merci
Vaska el gato
36

De plus grands projets C ++ que j'ai vu à peine utilisé plus d'un espace de noms (par exemple, une bibliothèque boost).

En fait, boost utilise des tonnes d'espaces de noms, généralement chaque partie de boost a son propre espace de noms pour le fonctionnement interne et peut ensuite ne mettre que l'interface publique dans le boost d'espace de noms de niveau supérieur.

Personnellement, je pense que plus une base de code est grande, plus les espaces de noms deviennent importants, même au sein d'une seule application (ou bibliothèque). Au travail, nous mettons chaque module de notre application dans son propre espace de noms.

Une autre utilisation (sans jeu de mots) des espaces de noms que j'utilise beaucoup est l'espace de noms anonyme:

namespace {
  const int CONSTANT = 42;
}

C'est essentiellement la même chose que:

static const int CONSTANT = 42;

L'utilisation d'un espace de noms anonyme (au lieu de statique) est cependant la méthode recommandée pour que le code et les données ne soient visibles que dans l'unité de compilation actuelle en C ++.


la source
13
Vos deux exemples sont équivalents à const int CONSTANT = 42;car la constante de niveau supérieur dans une étendue d'espace de noms implique déjà une liaison interne. Vous n'avez donc pas besoin de l'espace de noms anonyme dans ce cas.
sellibitze
19

Notez également que vous pouvez ajouter à un espace de noms. C'est plus clair avec un exemple, ce que je veux dire, c'est que vous pouvez avoir:

namespace MyNamespace
{
    double square(double x) { return x * x; }
}

dans un fichier square.h, et

namespace MyNamespace
{
    double cube(double x) { return x * x * x; }
}

dans un fichier cube.h. Cela définit un seul espace de noms MyNamespace(c'est-à-dire que vous pouvez définir un seul espace de noms sur plusieurs fichiers).

OysterD
la source
11

En Java:

package somepackage;
class SomeClass {}

En C ++:

namespace somenamespace {
    class SomeClass {}
}

Et en les utilisant, Java:

import somepackage;

Et C ++:

using namespace somenamespace;

De plus, les noms complets sont "somepackge.SomeClass" pour Java et "somenamespace :: SomeClass" pour C ++. En utilisant ces conventions, vous pouvez organiser comme vous en avez l'habitude en Java, notamment en créant des noms de dossier correspondants pour les espaces de noms. Les exigences de dossier-> package et file-> class ne sont pas là, donc vous pouvez nommer vos dossiers et classes indépendamment des packages et des espaces de noms.

Staale
la source
6

@ marius

Oui, vous pouvez utiliser plusieurs espaces de noms à la fois, par exemple:

using namespace boost;   
using namespace std;  

shared_ptr<int> p(new int(1));   // shared_ptr belongs to boost   
cout << "cout belongs to std::" << endl;   // cout and endl are in std

[Fév. 2014 - (Cela fait-il vraiment si longtemps?): Cet exemple particulier est maintenant ambigu, comme le souligne Joey ci-dessous. Boost et std :: maintenant ont chacun un shared_ptr.]

Adam Hollidge
la source
2
Notez que c'est stdégalement le cas shared_ptrmaintenant, donc l'utilisation des deux boostet des stdespaces de noms se heurtera lorsque vous essayez d'utiliser un shared_ptr.
Joey
2
C'est un bon exemple de la raison pour laquelle de nombreux éditeurs de logiciels découragent d'importer des espaces de noms entiers de cette manière. Cela ne fait pas de mal de toujours spécifier l'espace de noms, et s'ils sont trop longs, créer un alias ou uniquement des classes spécifiques importantes à partir de l'espace de noms.
paddy
5

Vous pouvez également contenir "using namespace ..." dans une fonction par exemple:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}
Shadow2531
la source
3

De manière générale, je crée un espace de noms pour un corps de code si je pense qu'il peut y avoir des conflits de noms de fonctions ou de types avec d'autres bibliothèques. Il aide également à marquer le code, ala boost :: .

Adam Hollidge
la source
3

Je préfère utiliser un espace de noms de niveau supérieur pour l'application et des sous-espaces de noms pour les composants.

La façon dont vous pouvez utiliser des classes à partir d'autres espaces de noms est étonnamment très similaire à celle de Java. Vous pouvez soit utiliser "use NAMESPACE" qui est similaire à une instruction "import PACKAGE", par exemple use std. Ou vous spécifiez le package comme préfixe de la classe séparée par "::", par exemple std :: string. Ceci est similaire à "java.lang.String" en Java.

dmeister
la source
3

Notez qu'un espace de noms en C ++ n'est vraiment qu'un espace de noms. Ils ne fournissent aucune des encapsulations que font les packages en Java, vous ne les utiliserez donc probablement pas autant.

Kristopher Johnson
la source
2

J'ai utilisé des espaces de noms C ++ de la même manière que je le fais en C #, Perl, etc. C'est juste une séparation sémantique de symboles entre des éléments de bibliothèque standard, des éléments tiers et mon propre code. Je placerais ma propre application dans un espace de noms, puis un composant de bibliothèque réutilisable dans un autre espace de noms pour la séparation.

spoulson
la source
2

Une autre différence entre java et C ++ est qu'en C ++, la hiérarchie de l'espace de noms n'a pas besoin de mach la disposition du système de fichiers. J'ai donc tendance à mettre une bibliothèque réutilisable entière dans un seul espace de noms et des sous-systèmes dans la bibliothèque dans des sous-répertoires:

#include "lib/module1.h"
#include "lib/module2.h"

lib::class1 *v = new lib::class1();

Je ne mettrais les sous-systèmes dans des espaces de noms imbriqués que s'il y avait une possibilité de conflit de noms.

KeithB
la source