Przestrzenie nazw (C++)

Przestrzeń nazw to region deklaratywny, który udostępnia zakres identyfikatorom (nazwy typów, funkcji, zmiennych itp.) wewnątrz niego. Przestrzenie nazw służą do organizowania kodu w grupy logiczne i zapobiegania kolizjom nazw, które mogą wystąpić szczególnie wtedy, gdy baza kodu zawiera wiele bibliotek. Wszystkie identyfikatory w zakresie przestrzeni nazw są widoczne dla siebie bez kwalifikacji. Identyfikatory spoza przestrzeni nazw mogą uzyskiwać dostęp do elementów członkowskich przy użyciu w pełni kwalifikowanej nazwy dla każdego identyfikatora, na przykład std::vector<std::string> vec;, lub za pomocą deklaracji dla pojedynczego identyfikatora (using std::string) lub przy użyciu dyrektywy dla wszystkich identyfikatorów w przestrzeni nazw (using namespace std;). Kod w plikach nagłówków powinien zawsze używać w pełni kwalifikowanej nazwy przestrzeni nazw.

W poniższym przykładzie pokazano deklarację przestrzeni nazw i trzy sposoby uzyskiwania dostępu do ich elementów członkowskich poza przestrzenią nazw.

namespace ContosoData
{
    class ObjectManager
    {
    public:
        void DoSomething() {}
    };
    void Func(ObjectManager) {}
}

Użyj w pełni kwalifikowanej nazwy:

ContosoData::ObjectManager mgr;
mgr.DoSomething();
ContosoData::Func(mgr);

Użyj deklaracji using, aby wprowadzić jeden identyfikator do zakresu:

using ContosoData::ObjectManager;
ObjectManager mgr;
mgr.DoSomething();

Użyj dyrektywy using, aby wprowadzić wszystko w przestrzeni nazw do zakresu:

using namespace ContosoData;

ObjectManager mgr;
mgr.DoSomething();
Func(mgr);

używanie dyrektyw

Dyrektywa using umożliwia używanie wszystkich nazw w obiekcie namespace bez nazwy przestrzeni nazw jako jawnego kwalifikatora. Użyj dyrektywy using w pliku implementacji (tj. *.cpp), jeśli używasz kilku różnych identyfikatorów w przestrzeni nazw; Jeśli używasz tylko jednego lub dwóch identyfikatorów, rozważ użycie deklaracji , aby przenieść te identyfikatory tylko do zakresu, a nie wszystkie identyfikatory w przestrzeni nazw. Jeśli zmienna lokalna ma taką samą nazwę jak zmienna przestrzeni nazw, zmienna przestrzeni nazw jest ukryta. Jest to błąd, aby mieć zmienną przestrzeni nazw o tej samej nazwie co zmienna globalna.

Uwaga

Dyrektywę using można umieścić w górnej części pliku cpp (w zakresie pliku) lub wewnątrz klasy lub definicji funkcji.

Ogólnie rzecz biorąc, należy unikać umieszczania dyrektyw w plikach nagłówków (*.h), ponieważ każdy plik zawierający ten nagłówek spowoduje wprowadzenie wszystkiego w przestrzeni nazw do zakresu, co może spowodować problemy z ukrywaniem nazw i konfliktami nazw, które są bardzo trudne do debugowania. Zawsze używaj w pełni kwalifikowanych nazw w pliku nagłówka. Jeśli nazwy te będą zbyt długie, możesz użyć aliasu przestrzeni nazw, aby je skrócić. (Zobacz poniżej).

Deklarowanie przestrzeni nazw i elementów członkowskich przestrzeni nazw

Zazwyczaj należy zadeklarować przestrzeń nazw w pliku nagłówka. Jeśli implementacje funkcji znajdują się w osobnym pliku, należy zakwalifikować nazwy funkcji, jak w tym przykładzie.

// contosoData.h
#pragma once
namespace ContosoDataServer
{
    void Foo();
    int Bar();
}

Implementacje funkcji w pliku contosodata.cpp powinny używać w pełni kwalifikowanej nazwy, nawet jeśli umieścisz dyrektywę using w górnej części pliku:

#include "contosodata.h"
using namespace ContosoDataServer;

void ContosoDataServer::Foo() // use fully-qualified name here
{
   // no qualification needed for Bar()
   Bar();
}

int ContosoDataServer::Bar(){return 0;}

Przestrzeń nazw można zadeklarować w wielu blokach w jednym pliku i w wielu plikach. Kompilator łączy części razem podczas przetwarzania wstępnego, a wynikowa przestrzeń nazw zawiera wszystkie elementy członkowskie zadeklarowane we wszystkich częściach. Przykładem jest przestrzeń nazw std zadeklarowana w każdym z plików nagłówka w standardowej bibliotece.

Elementy członkowskie nazwanej przestrzeni nazw można zdefiniować poza przestrzenią nazw, w której są deklarowane przez jawną kwalifikację zdefiniowanej nazwy. Jednak definicja musi pojawić się po punkcie deklaracji w przestrzeni nazw, która otacza przestrzeń nazw deklaracji. Na przykład:

// defining_namespace_members.cpp
// C2039 expected
namespace V {
    void f();
}

void V::f() { }        // ok
void V::g() { }        // C2039, g() is not yet a member of V

namespace V {
    void g();
}

Ten błąd może wystąpić, gdy elementy członkowskie przestrzeni nazw są deklarowane w wielu plikach nagłówków i nie zostały uwzględnione w odpowiedniej kolejności.

Globalna przestrzeń nazw

Jeśli identyfikator nie jest zadeklarowany w jawnej przestrzeni nazw, jest częścią niejawnej globalnej przestrzeni nazw. Ogólnie rzecz biorąc, należy unikać tworzenia deklaracji w zakresie globalnym, jeśli jest to możliwe, z wyjątkiem głównej funkcji punktu wejścia, która jest wymagana do pełnienia globalnej przestrzeni nazw. Aby jawnie zakwalifikować identyfikator globalny, użyj operatora rozpoznawania zakresu bez nazwy, jak w pliku ::SomeFunction(x);. Spowoduje to odróżnienie identyfikatora od dowolnej nazwy o tej samej nazwie w dowolnej innej przestrzeni nazw, a także ułatwi zrozumienie kodu przez inne osoby.

Przestrzeń nazw std

Wszystkie standardowe typy bibliotek i funkcje języka C++ są deklarowane w std przestrzeni nazw lub przestrzeniach nazw zagnieżdżonych wewnątrz stdelementu .

Zagnieżdżone przestrzenie nazw

Przestrzenie nazw mogą być zagnieżdżone. Zwykła zagnieżdżona przestrzeń nazw ma niekwalifikowany dostęp do elementów członkowskich elementu nadrzędnego, ale członkowie nadrzędni nie mają niekwalifikowanego dostępu do zagnieżdżonej przestrzeni nazw (chyba że jest zadeklarowana jako wbudowany), jak pokazano w poniższym przykładzie:

namespace ContosoDataServer
{
    void Foo();

    namespace Details
    {
        int CountImpl;
        void Ban() { return Foo(); }
    }

    int Bar(){...};
    int Baz(int i) { return Details::CountImpl; }
}

Zwykłe zagnieżdżone przestrzenie nazw mogą służyć do hermetyzacji wewnętrznych szczegółów implementacji, które nie są częścią publicznego interfejsu nadrzędnej przestrzeni nazw.

Wbudowane przestrzenie nazw (C++11)

W przeciwieństwie do zwykłego zagnieżdżonego obszaru nazw elementy członkowskie wbudowanej przestrzeni nazw są traktowane jako elementy członkowskie nadrzędnej przestrzeni nazw. Ta cecha umożliwia wyszukiwanie zależne od argumentów na przeciążonych funkcjach, które mają przeciążenia w obiekcie nadrzędnym i zagnieżdżonej wbudowanej przestrzeni nazw. Umożliwia również deklarowanie specjalizacji w nadrzędnej przestrzeni nazw dla szablonu zadeklarowanego w wbudowanej przestrzeni nazw. W poniższym przykładzie pokazano, jak kod zewnętrzny jest domyślnie powiązany z śródliniową przestrzenią nazw:

// Header.h
#include <string>

namespace Test
{
    namespace old_ns
    {
        std::string Func() { return std::string("Hello from old"); }
    }

    inline namespace new_ns
    {
        std::string Func() { return std::string("Hello from new"); }
    }
}

// main.cpp
#include "header.h"
#include <string>
#include <iostream>

int main()
{
    using namespace Test;
    using namespace std;

    string s = Func();
    std::cout << s << std::endl; // "Hello from new"
    return 0;
}

W poniższym przykładzie pokazano, jak można zadeklarować specjalizację w obiekcie nadrzędnym szablonu zadeklarowanego w wbudowanej przestrzeni nazw:

namespace Parent
{
    inline namespace new_ns
    {
         template <typename T>
         struct C
         {
             T member;
         };
    }
     template<>
     class C<int> {};
}

Wbudowane przestrzenie nazw można używać jako mechanizmu przechowywania wersji, aby zarządzać zmianami w interfejsie publicznym biblioteki. Można na przykład utworzyć jedną nadrzędną przestrzeń nazw i hermetyzować każdą wersję interfejsu we własnej przestrzeni nazw zagnieżdżonej wewnątrz elementu nadrzędnego. Przestrzeń nazw, która zawiera najnowszą lub preferowaną wersję, jest kwalifikowana jako wbudowana i dlatego jest widoczna tak, jakby była bezpośrednim elementem członkowskim nadrzędnej przestrzeni nazw. Kod klienta, który wywołuje klasę Parent::Class, zostanie automatycznie powiązany z nowym kodem. Klienci, którzy wolą używać starszej wersji, mogą nadal uzyskiwać do niej dostęp przy użyciu w pełni kwalifikowanej ścieżki do zagnieżdżonej przestrzeni nazw, która ma ten kod.

Słowo kluczowe wbudowane musi być stosowane do pierwszej deklaracji przestrzeni nazw w jednostce kompilacji.

W poniższym przykładzie przedstawiono dwie wersje interfejsu, z których każda znajduje się w zagnieżdżonej przestrzeni nazw. v_20 Przestrzeń nazw ma pewne modyfikacje z interfejsu v_10 i jest oznaczona jako wbudowany. Kod klienta korzystający z nowej biblioteki i wywołania wywołań Contoso::Funcs::Add wywoła v_20 wersji. Kod, który próbuje wywołać Contoso::Funcs::Divide , spowoduje teraz wyświetlenie błędu czasu kompilacji. Jeśli naprawdę potrzebują tej funkcji, nadal mogą uzyskać dostęp do v_10 wersji, jawnie wywołując funkcję Contoso::v_10::Funcs::Divide.

namespace Contoso
{
    namespace v_10
    {
        template <typename T>
        class Funcs
        {
        public:
            Funcs(void);
            T Add(T a, T b);
            T Subtract(T a, T b);
            T Multiply(T a, T b);
            T Divide(T a, T b);
        };
    }

    inline namespace v_20
    {
        template <typename T>
        class Funcs
        {
        public:
            Funcs(void);
            T Add(T a, T b);
            T Subtract(T a, T b);
            T Multiply(T a, T b);
            std::vector<double> Log(double);
            T Accumulate(std::vector<T> nums);
        };
    }
}

Aliasy przestrzeni nazw

Nazwy przestrzeni nazw muszą być unikatowe, co oznacza, że często nie powinny być zbyt krótkie. Jeśli długość nazwy sprawia, że kod jest trudny do odczytania lub jest żmudny do wpisywania w pliku nagłówka, w którym nie można używać dyrektyw, możesz utworzyć alias przestrzeni nazw, który służy jako skrót dla rzeczywistej nazwy. Na przykład:

namespace a_very_long_namespace_name { class Foo {}; }
namespace AVLNN = a_very_long_namespace_name;
void Bar(AVLNN::Foo foo){ }

anonimowe lub nienazwane przestrzenie nazw

Możesz utworzyć jawną przestrzeń nazw, ale nie nadać jej nazwę:

namespace
{
    int MyFunc(){}
}

Jest to nazywane nienazwaną lub anonimową przestrzenią nazw i jest przydatne, gdy chcesz, aby deklaracje zmiennych były niewidoczne dla kodu w innych plikach (tj. nadać im połączenie wewnętrzne) bez konieczności tworzenia nazwanej przestrzeni nazw. Cały kod w tym samym pliku może zobaczyć identyfikatory w przestrzeni nazw bez nazwy, ale identyfikatory, wraz z samą przestrzenią nazw, nie są widoczne poza tym plikiem — lub dokładniej poza jednostką tłumaczenia.

Zobacz też

Deklaracje i definicje