Jak właściwie używasz przestrzeni nazw w C ++?

231

Pochodzę z środowiska Java, w którym używane są pakiety, a nie przestrzenie nazw. Przyzwyczaiłem się do łączenia klas, które współpracują ze sobą, aby utworzyć kompletny obiekt w pakiety, a następnie ponownego ich używania z tego pakietu. Ale teraz pracuję w C ++.

Jak korzystasz z przestrzeni nazw w C ++? Czy tworzysz jedną przestrzeń nazw dla całej aplikacji, czy tworzysz przestrzenie nazw dla głównych komponentów? Jeśli tak, to w jaki sposób tworzysz obiekty z klas w innych przestrzeniach nazw?

Marius
źródło

Odpowiedzi:

167

Przestrzenie nazw są zasadniczo pakietami. Można ich używać w następujący sposób:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Następnie w kodzie:

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

Lub jeśli chcesz zawsze używać określonej przestrzeni nazw, możesz to zrobić:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Edycja: Zgodnie z tym, co powiedział bernhardrusch , zwykle nie używam w ogóle składni „using namespace x”, zwykle jawnie określam przestrzeń nazw podczas tworzenia instancji moich obiektów (tj. Pierwszego pokazanego przykładu).

I jak zapytałeś poniżej , możesz użyć tyle przestrzeni nazw, ile chcesz.

Mark Ingram
źródło
25
IMO lepiej jest przyzwyczaić się do prefiksowania stdprzestrzeni nazw symbolami niż usingw ogóle używania . Więc zawsze piszę std::coutlub std::stringteraz, bo tak je teraz nazywam. Nigdy bym po prostu nie napisał cout.
Tom Savage,
5
Chociaż jest to bardzo prawdziwe std, osobiście uważam, że jest to o wiele mniej ważne, gdy masz do czynienia z mniejszymi bibliotekami. Często możesz po prostu użyć using namespace FooBario;, szczególnie jeśli używasz znacznej liczby typów z biblioteki.
jkerian
4
@jkerian, rozumiem twój punkt widzenia, ale nie zgadzam się, ponieważ kolizje nazw są (moim zdaniem) bardziej prawdopodobne, że pochodzą z tak małych bibliotek. Większość ludzi stara się nie nazywać klas / funkcji takimi samymi jak te w STL. Mimo to zgadzam się, że using namespace X;należy unikać plików nagłówkowych, jeśli to możliwe.
Alan Turing
12
@LexFridman „Większość ludzi stara się nie nazywać klas / funkcji takimi samymi, jak te w STL” - to NIE JEST PRAWDĄ. Na przykład, gdybym napisał jakiś bardzo wyspecjalizowany kod we / wy dla jakiegoś dziwnego sprzętu, nigdy nie użyłbym niczego innego niż mylibrary::endldo reprezentowania mojej własnej specjalnej sekwencji nowego wiersza. To znaczy, po co wymyślać nazwy?
Mój kompilator nadal nie rozpoznaje przestrzeni nazw, nawet jeśli chcę to wyraźnie określić i dołączam plik, w którym został zadeklarowany.
bgenchel,
116

Aby uniknąć mówienia wszystkiego Mark Ingram powiedział już małą wskazówkę dotyczącą korzystania z przestrzeni nazw:

Unikaj dyrektywy „używanie przestrzeni nazw” w plikach nagłówków - otwiera to przestrzeń nazw dla wszystkich części programu, które importują ten plik nagłówka. W plikach implementacyjnych (* .cpp) zwykle nie jest to duży problem - chociaż wolę używać dyrektywy „using namespace” na poziomie funkcji.

Myślę, że przestrzenie nazw są najczęściej używane w celu uniknięcia konfliktów nazw - niekoniecznie w celu uporządkowania struktury kodu. Organizowałbym programy C ++ głównie z plikami nagłówkowymi / strukturą plików.

Czasami przestrzenie nazw są używane w większych projektach C ++ do ukrywania szczegółów implementacji.

Uwaga dodatkowa do dyrektywy używającej: Niektóre osoby wolą używać „używania” tylko dla pojedynczych elementów:

using std::cout;  
using std::endl;
Bernhardrusch
źródło
2
Jedną z zalet „używania przestrzeni nazw” na poziomie funkcji, jak sugerujesz, a nie na poziomie pliku .cpp lub poziomu bloku przestrzeni nazw {} w .cpp, jest to, że bardzo pomaga w kompilacjach z pojedynczą kompilacją. „używanie przestrzeni nazw” jest przechodnie i dotyczy przestrzeni nazw A w odrębnych blokach przestrzeni nazw A {} w tej samej jednostce, więc w przypadku kompilacji z pojedynczą kompilacją szybko skończysz używać wszystkiego, jeśli są wykonywane na poziomie pliku lub bloku przestrzeni nazw.
idij
using std::cout; to deklaracja użycia
Konstantin
3
Czy w jednej instrukcji można użyć kilku nazw z jednej przestrzeni nazw? Coś jak using std::cout, std::endl;lub nawet using std::cout, endl;.
AlQuemist
Można użyć znaku using namespace xw nagłówku, jeśli znajduje się on w innej przestrzeni nazw. Nie jest to coś, co ogólnie poleciłbym, ale nie zanieczyszcza globalnej przestrzeni nazw.
Praxeolitic
79

Vincent Robert ma rację w swoim komentarzu Jak prawidłowo używać przestrzeni nazw w C ++? .

Korzystanie z przestrzeni nazw

Przestrzenie nazw są używane przynajmniej w celu uniknięcia kolizji nazw. W Javie jest to wymuszane przez idiom „org.domain” (ponieważ przypuszcza się, że nie użyje się niczego poza własną nazwą domeny).

W C ++ można nadać przestrzeń nazw dla całego kodu w module. Na przykład dla modułu MyModule.dll można nadać jego kodowi przestrzeń nazw MyModule. Widziałem gdzie indziej kogoś używającego MyCompany :: MyProject :: MyModule. Myślę, że to przesada, ale w sumie wydaje mi się to poprawne.

Za pomocą „za pomocą”

Używanie powinno być stosowane z dużą ostrożnością, ponieważ skutecznie importuje jeden (lub wszystkie) symbole z przestrzeni nazw do bieżącej przestrzeni nazw.

Złe jest to robić w pliku nagłówkowym, ponieważ nagłówek będzie skaził każde źródło, w tym (przypomina mi makra ...), a nawet w pliku źródłowym zły styl poza zakresem funkcji, ponieważ będzie importować w zakresie globalnym symbole z przestrzeni nazw.

Najbezpieczniejszym sposobem użycia „za pomocą” jest zaimportowanie wybranych symboli:

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

Zobaczysz wiele „za pomocą przestrzeni nazw std;” w samouczku lub przykładowych kodach. Powodem jest zmniejszenie liczby symboli, aby ułatwić czytanie, nie dlatego, że jest to dobry pomysł.

„using namespace std;” jest zniechęcany przez Scotta Meyersa (nie pamiętam dokładnie, która książka, ale w razie potrzeby mogę ją znaleźć).

Skład przestrzeni nazw

Przestrzenie nazw to więcej niż paczki. Kolejny przykład można znaleźć w „The C ++ Programming Language” Bjarne Stroustrup.

W „Edycji specjalnej”, w 8.2.8 Skład przestrzeni nazw , opisuje, w jaki sposób można połączyć dwie przestrzenie nazw AAA i BBB w inną zwaną CCC. W ten sposób CCC staje się aliasem zarówno dla AAA, jak i BBB:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

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

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

Możesz nawet importować wybrane symbole z różnych przestrzeni nazw, aby zbudować własny interfejs przestrzeni nazw. Nie znalazłem jeszcze praktycznego zastosowania tego, ale teoretycznie jest fajnie.

paercebal
źródło
Czy możesz to wyjaśnić, „podaj przestrzeń nazw dla całego kodu w module”? Jaka jest dobra praktyka kapsułkowania w module. Na przykład mam klasę liczb zespolonych i funkcje zewnętrzne związane z liczbami zespolonymi. Ta klasa i te dwie funkcje powinny znajdować się w jednej przestrzeni nazw?
yanpas,
74

Nie widziałem żadnej wzmianki o tym w innych odpowiedziach, więc oto moje 2 centy kanadyjskie:

W temacie „korzystanie z przestrzeni nazw” użyteczną instrukcją jest alias przestrzeni nazw, pozwalający na „zmianę nazwy” przestrzeni nazw, zwykle w celu nadania jej krótszej nazwy. Na przykład zamiast:

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

Możesz pisać:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
Éric Malenfant
źródło
55

Nie słuchaj każdego, kto mówi ci, że przestrzenie nazw to tylko przestrzenie nazw.

Są ważne, ponieważ kompilator uważa, że ​​stosują zasadę interfejsu. Zasadniczo można to wyjaśnić przykładem:

namespace ns {

class A
{
};

void print(A a)
{
}

}

Jeśli chcesz wydrukować obiekt A, kod będzie następujący:

ns::A a;
print(a);

Zauważ, że nie wspomnieliśmy jawnie o przestrzeni nazw podczas wywoływania funkcji. Oto zasada interfejsu: C ++ uważa funkcję przyjmującą typ za argument za część interfejsu tego typu, więc nie trzeba określać przestrzeni nazw, ponieważ parametr już implikował przestrzeń nazw.

Dlaczego ta zasada jest ważna? Wyobraź sobie, że autor klasy A nie udostępnił funkcji print () dla tej klasy. Musisz sam go dostarczyć. Ponieważ jesteś dobrym programistą, zdefiniujesz tę funkcję we własnej przestrzeni nazw, a może w globalnej przestrzeni nazw.

namespace ns {

class A
{
};

}

void print(A a)
{
}

Twój kod może zacząć wywoływać funkcję print (a) w dowolnym miejscu. Teraz wyobraź sobie, że lata później autor decyduje się na udostępnienie funkcji print (), lepiej niż twoja, ponieważ zna elementy wewnętrzne swojej klasy i może stworzyć lepszą wersję niż twoja.

Następnie autorzy C ++ zdecydowali, że jego wersja funkcji print () powinna być używana zamiast tej podanej w innej przestrzeni nazw, aby przestrzegać zasady interfejsu. I że to „uaktualnienie” funkcji print () powinno być tak proste, jak to możliwe, co oznacza, że ​​nie będziesz musiał zmieniać każdego wywołania funkcji print (). Dlatego „funkcje interfejsu” (funkcje w tej samej przestrzeni nazw co klasa) mogą być wywoływane bez określania przestrzeni nazw w C ++.

I dlatego powinieneś rozważyć przestrzeń nazw C ++ jako „interfejs”, kiedy z niej korzystasz i pamiętaj o zasadzie interfejsu.

Jeśli chcesz lepiej wyjaśnić to zachowanie, możesz odnieść się do książki Exceptional C ++ z Herb Sutter

Vincent Robert
źródło
23
W rzeczywistości trzeba zmienić każde wywołanie print (), jeśli dodano ns :: Print, ale kompilator oznaczy każde wywołanie jako niejednoznaczne. Ciche przejście do nowej funkcji byłoby strasznym pomysłem.
Zaćmienie
Zastanawiam się teraz, mając to, co powiedział @Vincent, że będziesz musiał zmienić wszystkie wywołania do drukowania, jeśli autor zapewni funkcję ns :: Print (), co próbujesz powiedzieć? Że kiedy autor dodał funkcję ns :: Print (), możesz po prostu usunąć własną implementację? A może po prostu dodasz za pomocą ns :: print () using-statement? A może coś innego? Dzięki
Vaska el gato,
36

Większe projekty w C ++ prawie nigdy nie korzystałem z więcej niż jednej przestrzeni nazw (np. Biblioteka doładowań).

W rzeczywistości boost wykorzystuje mnóstwo przestrzeni nazw, zwykle każda część boosta ma swoją własną przestrzeń nazw dla wewnętrznych działań, a następnie może umieścić tylko interfejs publiczny w boost przestrzeni nazw najwyższego poziomu.

Osobiście uważam, że im większa baza kodu, tym ważniejsze stają się przestrzenie nazw, nawet w obrębie jednej aplikacji (lub biblioteki). W pracy każdy moduł naszej aplikacji umieszczamy we własnej przestrzeni nazw.

Innym zastosowaniem (bez zamiaru słów) przestrzeni nazw, z których często korzystam, jest anonimowa przestrzeń nazw:

namespace {
  const int CONSTANT = 42;
}

Jest to w zasadzie to samo, co:

static const int CONSTANT = 42;

Korzystanie z anonimowej przestrzeni nazw (zamiast statycznej) jest jednak zalecanym sposobem, aby kod i dane były widoczne tylko w bieżącej jednostce kompilacji w C ++.


źródło
13
Oba twoje przykłady są równoważne, const int CONSTANT = 42;ponieważ const najwyższego poziomu w zakresie przestrzeni nazw już implikuje powiązanie wewnętrzne. W tym przypadku nie potrzebujesz anonimowej przestrzeni nazw.
sellibitze
19

Pamiętaj też, że możesz dodać do przestrzeni nazw. To jest jaśniejsze na przykładzie, mam na myśli to, że możesz mieć:

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

w pliku square.hi

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

w pliku cube.h. Definiuje to pojedynczą przestrzeń nazw MyNamespace(to znaczy możesz zdefiniować jedną przestrzeń nazw dla wielu plików).

OysterD
źródło
11

W Javie:

package somepackage;
class SomeClass {}

W C ++:

namespace somenamespace {
    class SomeClass {}
}

I używając ich, Java:

import somepackage;

I C ++:

using namespace somenamespace;

Ponadto pełne nazwy to „somepackge.SomeClass” dla Java i „somenamespace :: SomeClass” dla C ++. Korzystając z tych konwencji, możesz organizować się tak, jak do tej pory jesteś przyzwyczajony w Javie, włączając w to tworzenie pasujących nazw folderów dla przestrzeni nazw. Nie ma jednak wymagań dotyczących folderów -> pakietów i plików -> klas, więc możesz nazwać swoje foldery i klasy niezależnie od pakietów i przestrzeni nazw.

Staale
źródło
6

@ marius

Tak, możesz używać jednocześnie kilku przestrzeni nazw, np .:

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

[Luty 2014 - (Czy to naprawdę trwało tak długo?): Ten konkretny przykład jest teraz niejednoznaczny, jak podkreśla Joey poniżej. Boost i std :: teraz mają teraz shared_ptr.]

Adam Hollidge
źródło
2
Należy zauważyć, że stdrównież shared_ptrteraz, więc przy użyciu zarówno boosti stdnazw będzie kolidować podczas próby użycia shared_ptr.
Joey,
2
Jest to dobry przykład tego, dlaczego wiele producentów oprogramowania zniechęca do importowania w ten sposób całych przestrzeni nazw. Nie zaszkodzi zawsze określać przestrzeń nazw, a jeśli są one zbyt długie, to tworzenie aliasu lub tylko ważnych określonych klas z przestrzeni nazw.
paddy
5

Możesz również zawierać „za pomocą przestrzeni nazw ...” wewnątrz funkcji, na przykład:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}
Shadow2531
źródło
3

Ogólnie rzecz biorąc, tworzę przestrzeń nazw dla części kodu, jeśli uważam, że może wystąpić konflikt nazw funkcji lub typu z innymi bibliotekami. Pomaga również znakować kod, ala boost :: .

Adam Hollidge
źródło
3

Wolę korzystać z przestrzeni nazw najwyższego poziomu dla aplikacji i podkategorii dla komponentów.

Sposób, w jaki możesz korzystać z klas z innych przestrzeni nazw, jest zaskakująco bardzo podobny do sposobu w java. Możesz albo użyć „use NAMESPACE”, który jest podobny do instrukcji „import PACKAGE”, np. Użyj std. Lub określasz pakiet jako przedrostek klasy oddzielony znakiem „::”, np. Std :: string. Jest to podobne do „java.lang.String” w Javie.

dmeister
źródło
3

Zauważ, że przestrzeń nazw w C ++ to tak naprawdę przestrzeń nazw. Nie zapewniają żadnej enkapsulacji wykonywanej przez pakiety w Javie, więc prawdopodobnie nie będziesz ich używać tak często.

Kristopher Johnson
źródło
2

Używałem przestrzeni nazw C ++ w taki sam sposób, jak w C #, Perl itp. Jest to tylko semantyczna separacja symboli między standardowymi bibliotekami, plikami stron trzecich i własnym kodem. Umieściłbym własną aplikację w jednej przestrzeni nazw, a następnie komponent biblioteki wielokrotnego użytku w innej przestrzeni nazw w celu rozdzielenia.

spoulson
źródło
2

Inną różnicą między Javą a C ++ jest to, że w C ++ hierarchia przestrzeni nazw nie musi modyfikować układu systemu plików. Dlatego staram się umieszczać całą bibliotekę wielokrotnego użytku w pojedynczej przestrzeni nazw, a podsystemy w bibliotece w podkatalogach:

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

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

Podsystemy umieściłbym w zagnieżdżonych przestrzeniach nazw tylko wtedy, gdyby istniała możliwość konfliktu nazw.

KeithB
źródło