Come usi correttamente gli spazi dei nomi in C ++?


231

Vengo da uno sfondo Java, in cui vengono utilizzati i pacchetti, non gli spazi dei nomi. Sono abituato a mettere insieme classi che lavorano insieme per formare un oggetto completo in pacchetti, per poi riutilizzarle in seguito da quel pacchetto. Ma ora sto lavorando in C ++.

Come si usano gli spazi dei nomi in C ++? Crei un singolo spazio dei nomi per l'intera applicazione o crei spazi dei nomi per i componenti principali? In tal caso, come si creano oggetti da classi in altri spazi dei nomi?

Risposte:


167

Gli spazi dei nomi sono essenzialmente pacchetti. Possono essere usati in questo modo:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Quindi nel codice:

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

Oppure, se vuoi usare sempre uno spazio dei nomi specifico, puoi farlo:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Modifica: seguendo quello che ha detto bernhardrusch , tendo a non usare affatto la sintassi "using namespace x", di solito specifichi esplicitamente lo spazio dei nomi quando creo un'istanza dei miei oggetti (cioè il primo esempio che ho mostrato).

E come hai chiesto di seguito , puoi utilizzare tutti gli spazi dei nomi che desideri.


25
IMO è meglio abituarsi al prefisso dello stdspazio dei nomi ai simboli piuttosto che usare usingaffatto. Quindi scrivo sempre std::couto std::stringora perché è quello che li chiamo adesso. Non scriverei mai cout.
Tom Savage,

5
Anche se questo è molto vero std, personalmente l'ho trovato molto meno importante quando hai a che fare con librerie più piccole. Spesso puoi semplicemente usare using namespace FooBario;, in particolare se stai usando un numero considerevole di tipi da una libreria.
jkerian,

4
@jkerian, vedo il tuo punto, ma non sono d'accordo perché le collisioni di nomi hanno (nella mia mente) più probabilità di provenire proprio da librerie così piccole. La maggior parte delle persone sta attenta a non nominare classi / funzioni come quelle di STL. Detto questo, sono d'accordo che using namespace X;dovrebbe essere evitato nei file di intestazione, se possibile.
Alan Turing,

12
@LexFridman "La maggior parte delle persone sta attenta a non nominare classi / funzioni uguali a quelle in STL", che NON È VERO. Ad esempio, se dovessi scrivere un codice I / O molto specializzato per uno strano hardware, non userei mai e poi altro che mylibrary::endlper rappresentare la mia speciale sequenza newline. Voglio dire, perché inventare i nomi?

Il mio compilatore non riconoscerà ancora lo spazio dei nomi, anche se voglio specificarlo esplicitamente e includo il file in cui è dichiarato.
bgenchel,

116

Per evitare di dire tutto Mark Ingram ha già detto un piccolo suggerimento per l'uso degli spazi dei nomi:

Evita la direttiva "utilizzo dello spazio dei nomi" nei file di intestazione: questo apre lo spazio dei nomi per tutte le parti del programma che importano questo file di intestazione. Nei file di implementazione (* .cpp) questo di solito non è un grosso problema, anche se preferisco usare la direttiva "using namespace" a livello di funzione.

Penso che gli spazi dei nomi siano usati principalmente per evitare conflitti di denominazione, non necessariamente per organizzare la struttura del codice. Organizzerei i programmi C ++ principalmente con i file header / la struttura dei file.

A volte gli spazi dei nomi vengono utilizzati nei progetti C ++ più grandi per nascondere i dettagli di implementazione.

Nota aggiuntiva alla direttiva using: alcune persone preferiscono usare "using" solo per singoli elementi:

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

2
Un vantaggio di "usare lo spazio dei nomi" a livello di funzione come suggerisci piuttosto che a livello di file .cpp o a livello di blocco dello spazio dei nomi {} all'interno di .cpp è che aiuta molto con le build di unità a compilazione singola. "utilizzo dello spazio dei nomi" è transitivo e si applica allo spazio dei nomi A attraverso blocchi dello spazio dei nomi A A {} nella stessa unità, quindi per le build con unità di compilazione singola si finisce rapidamente per utilizzare tutto se vengono eseguiti a livello di blocco di file o spazio dei nomi.
idij

using std::cout; è una dichiarazione di utilizzo
Konstantin

3
È possibile utilizzare più nomi da un singolo spazio dei nomi in una singola istruzione? Qualcosa di simile using std::cout, std::endl;o addirittura using std::cout, endl;,.
AlQuemist,

Può essere ok usare a using namespace xin un'intestazione se si trova in un altro spazio dei nomi. Non è qualcosa che consiglierei in generale, ma non inquina lo spazio dei nomi globale.
Prassolitico,

79

Vincent Robert ha ragione nel suo commento Come usi correttamente gli spazi dei nomi in C ++? .

Usando lo spazio dei nomi

Gli spazi dei nomi sono usati almeno per evitare la collisione dei nomi. In Java, questo viene applicato attraverso il linguaggio "org.domain" (perché si suppone che non si userà nient'altro che il proprio nome di dominio).

In C ++, potresti dare uno spazio dei nomi a tutto il codice nel tuo modulo. Ad esempio, per un modulo MyModule.dll, è possibile assegnare al suo codice lo spazio dei nomi MyModule. Ho visto altrove qualcuno che utilizza MyCompany :: MyProject :: MyModule. Immagino che sia eccessivo, ma tutto sommato, mi sembra corretto.

Usando "usando"

L'uso dovrebbe essere usato con grande cura perché importa in modo efficace uno (o tutti) simboli da uno spazio dei nomi nel tuo attuale spazio dei nomi.

Questo è male farlo in un file di intestazione perché la tua intestazione inquinerà ogni sorgente incluso (mi ricorda le macro ...), e anche in un file di origine, stile cattivo al di fuori di un ambito di funzione perché verrà importato in ambito globale i simboli dallo spazio dei nomi.

Il modo più sicuro di usare "usando" è importare simboli selezionati:

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 ;
}

Vedrai molto "usare lo spazio dei nomi std;" in tutorial o codici di esempio. Il motivo è ridurre il numero di simboli per facilitare la lettura, non perché è una buona idea.

"using namespace std;" è scoraggiato da Scott Meyers (non ricordo esattamente quale libro, ma posso trovarlo se necessario).

Composizione nello spazio dei nomi

Gli spazi dei nomi sono più che pacchetti. Un altro esempio può essere trovato in "Il linguaggio di programmazione C ++" di Bjarne Stroustrup.

Nella "Special Edition", in 8.2.8 Composizione dello spazio dei nomi , descrive come unire due spazi dei nomi AAA e BBB in un altro chiamato CCC. Quindi CCC diventa un alias sia per AAA che per BBB:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

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

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

È anche possibile importare simboli selezionati da diversi spazi dei nomi, per creare la propria interfaccia dello spazio dei nomi personalizzata. Devo ancora trovare un uso pratico di questo, ma in teoria è bello.


Potresti chiarire, per favore "dai uno spazio dei nomi a tutto il codice nel tuo modulo"? Qual è la buona pratica da incapsulare al modulo. Ad esempio ho una classe di numeri complessi e funzioni esterne relative a numeri complessi. Questa classe e quelle due funzioni dovrebbero trovarsi in uno spazio dei nomi?
yanpas,

74

Non ne ho visto alcun riferimento nelle altre risposte, quindi ecco i miei 2 centesimi canadesi:

Nell'argomento "utilizzo dello spazio dei nomi", un'istruzione utile è l'alias dello spazio dei nomi, che consente di "rinominare" uno spazio dei nomi, normalmente per assegnargli un nome più breve. Ad esempio, anziché:

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

tu puoi scrivere:

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

55

Non ascoltare tutte le persone che ti dicono che gli spazi dei nomi sono solo spazi dei nomi.

Sono importanti perché vengono considerati dal compilatore per applicare il principio dell'interfaccia. Fondamentalmente, può essere spiegato da un esempio:

namespace ns {

class A
{
};

void print(A a)
{
}

}

Se si desidera stampare un oggetto A, il codice sarebbe questo:

ns::A a;
print(a);

Si noti che non abbiamo menzionato esplicitamente lo spazio dei nomi quando si chiama la funzione. Questo è il principio dell'interfaccia: C ++ considera una funzione che prende un tipo come argomento come parte dell'interfaccia per quel tipo, quindi non è necessario specificare lo spazio dei nomi perché il parametro implicava già lo spazio dei nomi.

Ora, perché questo principio è importante? Immagina che l'autore della classe A non abbia fornito una funzione print () per questa classe. Dovrai fornirne uno tu stesso. Dato che sei un buon programmatore, definirai questa funzione nel tuo spazio dei nomi, o forse nello spazio dei nomi globale.

namespace ns {

class A
{
};

}

void print(A a)
{
}

E il tuo codice può iniziare a chiamare la funzione print (a) dove vuoi. Ora immagina che anni dopo, l'autore decide di fornire una funzione print (), migliore della tua perché conosce gli interni della sua classe e può fare una versione migliore della tua.

Quindi gli autori di C ++ hanno deciso di utilizzare la sua versione della funzione print () anziché quella fornita in un altro spazio dei nomi, per rispettare il principio dell'interfaccia. E che questo "upgrade" della funzione print () dovrebbe essere il più semplice possibile, il che significa che non dovrai cambiare ogni chiamata alla funzione print (). Ecco perché "funzioni di interfaccia" (funzione nello stesso spazio dei nomi di una classe) possono essere chiamate senza specificare lo spazio dei nomi in C ++.

Ed è per questo che dovresti considerare uno spazio dei nomi C ++ come "interfaccia" quando ne usi uno e tieni presente il principio dell'interfaccia.

Se vuoi una spiegazione migliore di questo comportamento, puoi fare riferimento al libro C ++ eccezionale di Herb Sutter


23
In realtà devi cambiare ogni chiamata a print () se viene aggiunto ns :: Print, ma il compilatore contrassegnerà ogni chiamata come ambigua. Passare silenziosamente alla nuova funzione sarebbe un'idea terribile.
Eclipse,

Mi chiedo ora, avendo quello che @Vincent ha detto che dovresti cambiare tutte le chiamate per stampare, se autor fornisse la funzione ns :: Print (), cosa stavi cercando di dire? Che quando l'autore ha aggiunto una funzione ns :: Print (), puoi semplicemente rimuovere la tua implementazione? O che aggiungerai semplicemente usando ns :: print () using-dichiarazioni? O domare altro? Grazie
Vaska el gato,

36

I progetti C ++ più grandi che ho visto difficilmente hanno utilizzato più di uno spazio dei nomi (ad es. Libreria di boost).

In realtà boost utilizza tonnellate di spazi dei nomi, in genere ogni parte di boost ha il proprio spazio dei nomi per i meccanismi interni e quindi può mettere solo l'interfaccia pubblica nel boost dello spazio dei nomi di livello superiore.

Personalmente penso che più grande diventa una base di codice, più diventano importanti gli spazi dei nomi, anche all'interno di una singola applicazione (o libreria). Al lavoro mettiamo ogni modulo della nostra applicazione nel suo spazio dei nomi.

Un altro uso (nessun gioco di parole previsto) di spazi dei nomi che uso molto è lo spazio dei nomi anonimo:

namespace {
  const int CONSTANT = 42;
}

Questo è fondamentalmente lo stesso di:

static const int CONSTANT = 42;

L'uso di uno spazio dei nomi anonimo (anziché statico) è tuttavia il modo consigliato per visualizzare codice e dati solo all'interno dell'unità di compilazione corrente in C ++.


13
Entrambi i tuoi esempi sono equivalenti a const int CONSTANT = 42;perché la const di livello superiore in un ambito dello spazio dei nomi implica già un collegamento interno. Quindi in questo caso non è necessario lo spazio dei nomi anonimo.
sellibitze,

19

Inoltre, tieni presente che puoi aggiungere uno spazio dei nomi. Questo è più chiaro con un esempio, ciò che intendo è che puoi avere:

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

in un file square.he

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

in un file cube.h. Ciò definisce un singolo spazio dei nomi MyNamespace(ovvero è possibile definire un singolo spazio dei nomi su più file).


11

In Java:

package somepackage;
class SomeClass {}

In C ++:

namespace somenamespace {
    class SomeClass {}
}

E usandoli, Java:

import somepackage;

E C ++:

using namespace somenamespace;

Inoltre, i nomi completi sono "somepackge.SomeClass" per Java e "somenamespace :: SomeClass" per C ++. Usando queste convenzioni, puoi organizzarti come sei abituato in Java, incluso creare nomi di cartelle corrispondenti per gli spazi dei nomi. I requisiti di cartella-> pacchetto e file-> non ci sono però, quindi puoi nominare le tue cartelle e classi indipendentemente da pacchetti e spazi dei nomi.


6

@ marius

Sì, è possibile utilizzare più spazi dei nomi alla volta, ad esempio:

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

[Febbraio 2014 - (È passato davvero tanto tempo?): Questo esempio particolare è ora ambiguo, come sottolinea Joey di seguito. Boost e std :: ora ognuno ha un shared_ptr.]


2
Si noti che stdha anche shared_ptrormai, quindi l'utilizzo di entrambi booste gli stdspazi dei nomi si scontreranno quando si tenta di utilizzare a shared_ptr.
Joey,

2
Questo è un buon esempio del perché molte case di software scoraggeranno l'importazione di interi spazi dei nomi in questo modo. Non è male specificare sempre lo spazio dei nomi e, se sono troppo lunghi, creare un alias o solo importanti classi specifiche dallo spazio dei nomi.
risaia

5

Puoi anche contenere "using namespace ..." all'interno di una funzione, ad esempio:

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

3

In generale, creo uno spazio dei nomi per un corpo di codice se credo che potrebbe esserci un conflitto di nome funzione o tipo con altre librerie. Aiuta anche a codice del marchio, ala boost :: .


3

Preferisco usare uno spazio dei nomi di livello superiore per l'applicazione e spazi dei nomi secondari per i componenti.

Il modo in cui è possibile utilizzare le classi da altri spazi dei nomi è sorprendentemente molto simile al modo in Java. Puoi usare "usa NAMESPACE" che è simile a un'istruzione "import PACKAGE", ad esempio usa std. Oppure si specifica il pacchetto come prefisso della classe separata da "::", ad es. Std :: string. Questo è simile a "java.lang.String" in Java.


3

Si noti che uno spazio dei nomi in C ++ è in realtà solo uno spazio dei nomi. Non forniscono alcun tipo di incapsulamento dei pacchetti in Java, quindi probabilmente non li userete tanto.


2

Ho usato gli spazi dei nomi C ++ nello stesso modo in cui lo faccio in C #, Perl, ecc. È solo una separazione semantica di simboli tra oggetti di libreria standard, oggetti di terze parti e il mio codice. Metterei la mia app in uno spazio dei nomi, quindi un componente riutilizzabile della libreria in un altro spazio dei nomi per la separazione.


2

Un'altra differenza tra java e C ++ è che in C ++ la gerarchia dello spazio dei nomi non ha bisogno di creare il layout del filesystem. Quindi tendo a mettere un'intera libreria riutilizzabile in un singolo spazio dei nomi e sottosistemi all'interno della libreria in sottodirectory:

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

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

Metterei i sottosistemi in spazi dei nomi nidificati solo se esistesse la possibilità di un conflitto di nomi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.