7.14 — Nienazwane i wbudowane przestrzenie nazw

C++ obsługuje dwa warianty przestrzeni nazw, o których warto przynajmniej wiedzieć. Nie będziemy się na nich opierać, więc potraktuj tę lekcję na razie jako opcjonalną.

Nienazwane (anonimowe) przestrzenie nazw

An nienazwana przestrzeń nazw (zwany także anonimowa przestrzeń nazw) to przestrzeń nazw zdefiniowana bez nazwy w następujący sposób:

#include <iostream>

namespace // unnamed namespace
{
    void doSomething() // can only be accessed in this file
    {
        std::cout << "v1\n";
    }
}

int main()
{
    doSomething(); // we can call doSomething() without a namespace prefix

    return 0;
}

Wypisuje:

v1

Cała treść zadeklarowana w nienazwanej przestrzeni nazw jest traktowana tak, jakby była częścią nadrzędnej przestrzeni nazw. Zatem mimo że funkcja doSomething() jest zdefiniowana w nienazwanej przestrzeni nazw, sama funkcja jest dostępna z nadrzędnej przestrzeni nazw (która w tym przypadku jest globalną przestrzenią nazw), dlatego możemy wywoływać doSomething() z main() bez żadnych kwalifikatorów.

Może to sprawić, że nienazwane przestrzenie nazw będą wydawać się bezużyteczne. Ale innym efektem nienazwanych przestrzeni nazw jest to, że wszystkie identyfikatory w nienazwanej przestrzeni nazw są traktowane tak, jakby miały wewnętrzne powiązanie, co oznacza, że ​​zawartości nienazwanej przestrzeni nazw nie można zobaczyć poza plikiem, w którym zdefiniowana jest nienazwana przestrzeń nazw.

W przypadku funkcji jest to w rzeczywistości to samo, co zdefiniowanie wszystkich funkcji w nienazwanej przestrzeni nazw jako funkcji statycznych. Poniższy program jest w rzeczywistości identyczny z powyższym:

#include <iostream>

static void doSomething() // can only be accessed in this file
{
    std::cout << "v1\n";
}

int main()
{
    doSomething(); // we can call doSomething() without a namespace prefix

    return 0;
}

Nienazwane przestrzenie nazw są zwykle używane, gdy masz dużo treści, które chcesz mieć pewność, że pozostaną lokalne dla danej jednostki tłumaczeniowej, ponieważ łatwiej jest zgrupować taką treść w jednej nienazwanej przestrzeni nazw, niż indywidualnie oznaczać wszystkie deklaracje jako static. Nienazwane przestrzenie nazw będą również przechowywać typy zdefiniowane w programie (coś, co omówimy w późniejszej lekcji) lokalnie w jednostce tłumaczącej, co jest czymś, dla czego nie ma alternatywnego równoważnego mechanizmu.

Wskazówka

Jeśli jesteś hardkorem, możesz zastosować odwrotne podejście — umieść całą zawartość, która nie jest jawnie przeznaczona do eksportu/zewnętrznego, w nienazwanej przestrzeni nazw.

Nienazwane przestrzenie nazw zasadniczo nie powinny być używane w nagłówku SEI CERT (zasada DCL59-CPP) podaje kilka dobrych przykładów wyjaśniających dlaczego.

Najlepsza praktyka

Preferuj nienazwane przestrzenie nazw, jeśli masz treść, którą chcesz zachować lokalnie w stosunku do jednostki tłumaczenia.

Unikaj nienazwanych przestrzeni nazw w plikach nagłówkowych.

Inline przestrzenie nazw

Rozważmy teraz następujący program:

#include <iostream>

void doSomething()
{
    std::cout << "v1\n";
}

int main()
{
    doSomething();

    return 0;
}

Wypisuje:

v1

To całkiem proste, prawda?

Ale powiedzmy, że nie jesteś zadowolony z doSomething() i chcesz go ulepszyć w jakiś sposób, który zmieni jego zachowanie. Ale jeśli to zrobisz, ryzykujesz uszkodzeniem istniejących programów przy użyciu starszej wersji. Jak sobie z tym poradzić?

Jednym ze sposobów byłoby utworzenie nowej wersji funkcji o innej nazwie. Jednak w wyniku wielu zmian możesz otrzymać cały zestaw funkcji o niemal identycznych nazwach (doSomething, doSomething_v2, doSomething_v3 itd.).

Alternatywą jest użycie wbudowanej przestrzeni nazw. wbudowana przestrzeń nazw to przestrzeń nazw, która jest zwykle używana do wersjonowania treści. Podobnie jak w przypadku nienazwanej przestrzeni nazw, wszystko zadeklarowane w wbudowanej przestrzeni nazw jest uważane za część nadrzędnej przestrzeni nazw. Jednak w przeciwieństwie do nienazwanych przestrzeni nazw, wbudowane przestrzenie nazw nie wpływają na powiązania.

Aby zdefiniować wewnętrzną przestrzeń nazw, używamy inline słowem kluczowym:

#include <iostream>

inline namespace V1 // declare an inline namespace named V1
{
    void doSomething()
    {
        std::cout << "V1\n";
    }
}

namespace V2 // declare a normal namespace named V2
{
    void doSomething()
    {
        std::cout << "V2\n";
    }
}

int main()
{
    V1::doSomething(); // calls the V1 version of doSomething()
    V2::doSomething(); // calls the V2 version of doSomething()

    doSomething(); // calls the inline version of doSomething() (which is V1)
 
    return 0;
}

Wypisuje:

V1
V2
V1

W powyższym przykładzie osoby wywołujące doSomething() dostaną wersję V1 (wersję wbudowaną) doSomething(). Osoby wywołujące, które chcą korzystać z nowszej wersji, mogą jawnie wywołać V2::doSomething(). Zachowuje to funkcje istniejących programów, jednocześnie umożliwiając nowszym programom korzystanie z nowszych/lepszych odmian.

Alternatywnie, jeśli chcesz opublikować nowszą wersję:

#include <iostream>

namespace V1 // declare a normal namespace named V1
{
    void doSomething()
    {
        std::cout << "V1\n";
    }
}

inline namespace V2 // declare an inline namespace named V2
{
    void doSomething()
    {
        std::cout << "V2\n";
    }
}

int main()
{
    V1::doSomething(); // calls the V1 version of doSomething()
    V2::doSomething(); // calls the V2 version of doSomething()

    doSomething(); // calls the inline version of doSomething() (which is V2)
 
    return 0;
}

Wypisuje:

V1
V2
V2

W tym przykładzie wszyscy wywołujący doSomething() domyślnie otrzymają wersję v2 (nowszą i lepszą wersję). Użytkownicy, którzy nadal chcą starszej wersji doSomething() , mogą jawnie wywołać V1::doSomething() , aby uzyskać dostęp do starego zachowania. Oznacza to, że istniejące programy, które chcą wersji V1, będą musiały globalnie zastąpić doSomething z V1::doSomething, ale zazwyczaj nie będzie to problematyczne, jeśli funkcje będą dobrze nazwane.

Mieszanie wbudowanej i nienazwanej przestrzeni nazw Opcjonalne

Przestrzeń nazw może być zarówno wbudowana, jak i nienazwana:

#include <iostream>

namespace V1 // declare a normal namespace named V1
{
    void doSomething()
    {
        std::cout << "V1\n";
    }
}

inline namespace // declare an inline unnamed namespace
{
    void doSomething() // has internal linkage
    {
        std::cout << "V2\n";
    }
}

int main()
{
    V1::doSomething(); // calls the V1 version of doSomething()
    // there is no V2 in this example, so we can't use V2:: as a namespace prefix

    doSomething(); // calls the inline version of doSomething() (which is the anonymous one)

    return 0;
}

Jednak w takich przypadkach prawdopodobnie lepiej jest zagnieździć anonimową przestrzeń nazw w wbudowanej przestrzeni nazw. Ma to ten sam efekt (wszystkie funkcje wewnątrz anonimowej przestrzeni nazw mają domyślnie wewnętrzne powiązania), ale nadal daje wyraźną nazwę przestrzeni nazw, której możesz użyć:

#include <iostream>

namespace V1 // declare a normal namespace named V1
{
    void doSomething()
    {
        std::cout << "V1\n";
    }
}

inline namespace V2 // declare an inline namespace named V2
{
    namespace // unnamed namespace
    {
        void doSomething() // has internal linkage
        {
            std::cout << "V2\n";
        }

    }
}

int main()
{
    V1::doSomething(); // calls the V1 version of doSomething()
    V2::doSomething(); // calls the V2 version of doSomething()

    doSomething(); // calls the inline version of doSomething() (which is V2)

    return 0;
}
guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
128 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze