2.8-Naming collisions and an introduction to namespaces

Załóżmy, że jedziesz do domu znajomego po raz pierwszy, a adres podany ci to 245 Front Street w Mill City. Po dotarciu do Mill City wyciągasz mapę, aby odkryć, że Mill City ma dwie różne ulice naprzeciwko siebie! Do którego byś poszedł? Chyba, że były jakieś dodatkowe wskazówki, które pomogą Ci w podjęciu decyzji (np. pamiętasz jego dom jest w pobliżu rzeki) musisz zadzwonić do przyjaciela i poprosić o więcej informacji. Ponieważ byłoby to mylące i nieefektywne (szczególnie dla listonosza), w większości krajów wszystkie nazwy ulic i adresy domów w mieście muszą być unikalne.

podobnie, C++ wymaga, aby wszystkie identyfikatory były niejednoznaczne. Jeśli dwa identyczne identyfikatory zostaną wprowadzone do tego samego programu w sposób, którego kompilator lub linker nie może odróżnić, kompilator lub linker spowoduje błąd. Ten błąd jest ogólnie określany jako kolizja nazw (lub konflikt nazw).

przykład kolizji nazw

a.cpp:

1
2
3
4
5
6

#include <iostream>
void myFcn(int x)
{
std:: cout << x;
}

główna.cpp:

1
2
3
4
5
6
7
8
9
10
11

#include <iostream>
void myFcn(int x)
{
std:: cout < < 2 * x;
}
int main()
{
return 0;
}

gdy kompilator skompiluje ten program, skompiluje a.cpp i główna.cpp niezależnie, a każdy plik skompiluje się bez problemów.

jednak, gdy linker wykona, połączy wszystkie definicje w a.cpp i główna.cpp razem i odkryć sprzeczne definicje funkcji myFcn. Łącznik zostanie przerwany z błędem. Zauważ, że ten błąd występuje, mimo że myFcn nigdy nie jest wywoływany!

Większość kolizji nazw występuje w dwóch przypadkach:
1) dwie (lub więcej) definicje funkcji (lub zmiennej globalnej) są wprowadzane do oddzielnych plików, które są kompilowane do tego samego programu. Spowoduje to błąd łącza, jak pokazano powyżej.
2) dwie (lub więcej) definicje funkcji (lub zmiennej globalnej) są wprowadzane do tego samego pliku (często poprzez #include). Spowoduje to błąd kompilatora.

w miarę jak programy stają się większe i używają więcej identyfikatorów, szanse na wprowadzenie kolizji nazw znacznie rosną. Dobrą wiadomością jest to, że C++ zapewnia wiele mechanizmów unikania kolizji nazw. Jednym z takich mechanizmów jest local scope, który utrzymuje zmienne lokalne zdefiniowane wewnątrz funkcji przed konfliktem ze sobą. Ale local scope nie działa dla nazw funkcji. Jak więc zachować sprzeczne ze sobą nazwy funkcji?

co to jest Przestrzeń nazw?

Wracając na chwilę do naszego adresu, posiadanie dwóch ulic frontowych było problematyczne tylko dlatego, że te ulice istniały w obrębie tego samego miasta. Z drugiej strony, gdybyś musiał dostarczyć pocztę na dwa adresy, jeden na 209 Front Street w Mill City i inny adres na 417 Front Street w Jonesville, nie byłoby zamieszania co do tego, gdzie iść. Innymi słowy, miasta zapewniają grupy, które pozwalają nam rozróżniać adresy, które w przeciwnym razie mogłyby ze sobą kolidować. Przestrzenie nazw działają tak, jak Miasta w tej analogii.

przestrzeń nazw jest regionem, który pozwala zadeklarować nazwy wewnątrz niej w celu disambiguacji. Przestrzeń nazw zapewnia zakres (zwany przestrzenią nazw) nazwom zadeklarowanym wewnątrz niej-co po prostu oznacza, że każda nazwa zadeklarowana wewnątrz przestrzeni nazw nie będzie mylona z identycznymi nazwami w innych zakresach .

kluczowe informacje

nazwa zadeklarowana w przestrzeni nazw nie będzie mylona z identyczną nazwą zadeklarowaną w innym zakresie.

w przestrzeni nazw wszystkie nazwy muszą być unikalne, w przeciwnym razie dojdzie do kolizji nazw.

Przestrzenie nazw są często używane do grupowania powiązanych identyfikatorów w dużym projekcie, aby zapewnić, że nie przypadkowo zderzą się z innymi identyfikatorami. Na przykład, jeśli umieścisz wszystkie funkcje matematyczne w przestrzeni nazw o nazwie math, funkcje matematyczne nie będą kolidować z identycznie nazwanymi funkcjami spoza przestrzeni nazw matematycznych.

porozmawiamy o tym, jak tworzyć własne przestrzenie nazw w przyszłej lekcji.

globalna przestrzeń nazw

w C++ każda nazwa, która nie jest zdefiniowana wewnątrz klasy, funkcji lub przestrzeni nazw, jest uważana za część domyślnie zdefiniowanej przestrzeni nazw nazywanej globalną przestrzenią nazw (czasami nazywaną globalnym zakresem).

w przykładzie na górze lekcji funkcje main() i obie wersje myFcn() są zdefiniowane wewnątrz globalnej przestrzeni nazw. Kolizja nazw napotkana w przykładzie ma miejsce, ponieważ obie wersje myfcn () kończą się wewnątrz globalnej przestrzeni nazw, co narusza zasadę, że wszystkie nazwy w przestrzeni nazw muszą być unikalne.

przestrzeń nazw std

kiedy C++ był pierwotnie projektowany, wszystkie identyfikatory w standardowej bibliotece C++ (w tym STD::cin i std::cout) były dostępne do użycia bez prefiksu std:: (były częścią globalnej przestrzeni nazw). Oznaczało to jednak, że każdy identyfikator w bibliotece standardowej może potencjalnie kolidować z dowolną nazwą wybraną dla własnych identyfikatorów (również zdefiniowaną w globalnej przestrzeni nazw). Kod, który działał, może nagle mieć konflikt nazw, gdy dołączysz nowy plik z biblioteki standardowej. Co gorsza, programy, które skompilowałyby się pod jedną z wersji C++, mogą nie kompilować się pod przyszłą wersją C++, ponieważ nowe identyfikatory wprowadzone do standardowej biblioteki mogą mieć konflikt nazw z już napisanym kodem. Tak więc C++ przeniósł wszystkie funkcje w bibliotece standardowej do przestrzeni nazw o nazwie ” std ” (skrót od standard).

okazuje się, że nazwa std::cout nie jest tak naprawdę std::cout. W rzeczywistości jest to tylko cout, a std jest nazwą przestrzeni nazw, której identyfikator cout jest częścią. Ponieważ cout jest zdefiniowany w przestrzeni nazw std, nazwa cout nie będzie kolidować z żadnymi obiektami lub funkcjami o nazwie cout, które tworzymy w globalnej przestrzeni nazw.

podobnie, gdy uzyskujesz dostęp do identyfikatora zdefiniowanego w przestrzeni nazw (np. std::cout) , musisz powiedzieć kompilatorowi, że szukamy identyfikatora zdefiniowanego w przestrzeni nazw (std).

Key insight

gdy używasz identyfikatora zdefiniowanego w przestrzeni nazw (takiej jak std namespace), musisz powiedzieć kompilatorowi, że identyfikator znajduje się w przestrzeni nazw.

istnieje kilka różnych sposobów, aby to zrobić.

jawny kwalifikator przestrzeni nazw std::

najprostszym sposobem, aby powiedzieć kompilatorowi, że chcemy użyć cout z przestrzeni nazw std, jest jawne użycie prefiksu std::. Na przykład:

1
2
3
4
5
6
7

#include < iostream>
int main()
{
std:: cout < < ” Hello world!”; // kiedy mówimy cout, mamy na myśli cout zdefiniowany w przestrzeni nazw std
return 0;
}

symbol:: jest operatorem zwanym operatorem rozdzielczości zakresu. Identyfikator po lewej stronie symbolu:: identyfikuje przestrzeń nazw, w której znajduje się nazwa po prawej stronie symbolu::. Jeśli nie podano identyfikatora po lewej stronie symbolu::, przyjmuje się globalną przestrzeń nazw.

więc kiedy mówimy std:: cout, mówimy „cout, który mieszka w przestrzeni nazw std”.

jest to najbezpieczniejszy sposób użycia cout, ponieważ nie ma dwuznaczności co do tego, do którego cout się odwołujemy (tego w przestrzeni nazw std).

najlepsze praktyki

użyj jawnych prefiksów przestrzeni nazw, aby uzyskać dostęp do identyfikatorów zdefiniowanych w przestrzeni nazw.

używanie std przestrzeni nazw (i dlaczego go unikać)

innym sposobem dostępu do identyfikatorów wewnątrz przestrzeni nazw jest użycie instrukcji using directive. Oto nasz oryginalny program „Hello world” z użyciem dyrektywy:

1
2
3
4
5
6
7
8
9

#include <iostream >
using namespace std; // jest to dyrektywa using nakazująca kompilatorowi sprawdzenie przestrzeni nazw std podczas rozwiązywania identyfikatorów bez prefiksu
int main()
{
cout << „Hello world!”; // cout nie ma prefiksu, więc kompilator sprawdzi czy cout jest zdefiniowany lokalnie czy w przestrzeni nazw std
return 0;
}

dyrektywa using nakazuje kompilatorowi sprawdzenie określonej przestrzeni nazw podczas próby rozwiązania identyfikatora, który nie ma prefiksu przestrzeni nazw. Tak więc w powyższym przykładzie, gdy kompilator zdecyduje, czym jest identyfikator cout, sprawdzi zarówno lokalnie (gdzie jest niezdefiniowany), jak i w przestrzeni nazw std (gdzie będzie pasował do std::cout).

wiele tekstów, samouczków, a nawet niektóre Kompilatory zalecają lub używają dyrektywy using na górze programu. Jednakże, stosowane w ten sposób, jest to zła praktyka i wysoce odradzane.

rozważ następujący program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <iostream > // importuje deklarację std::cout
używając przestrzeni nazw std; // sprawia, że std::cout jest dostępny jako „cout”
int cout () / / deklaruje naszą własną funkcję” cout”
{
return 5;
}
int main()
{
cout << „Witaj, świecie!”; / / Compile error! Który kurek chcemy tutaj? Ten w przestrzeni nazw std czy ten zdefiniowany powyżej?
powrót 0;
}

powyższy program nie kompiluje się, ponieważ kompilator nie może teraz stwierdzić, czy chcemy zdefiniowaną przez nas funkcję cout, czy też cout zdefiniowaną wewnątrz przestrzeni nazw std.

podczas używania dyrektywy using w ten sposób, każdy identyfikator, który zdefiniujemy, może być sprzeczny z każdym identycznie nazwanym identyfikatorem w przestrzeni nazw std. Co gorsza, chociaż nazwa identyfikatora nie może być obecnie konfliktowa, może to być konflikt z nowymi identyfikatorami dodanymi do przestrzeni nazw std w przyszłych wersjach językowych. To był cały cel przeniesienia wszystkich identyfikatorów w bibliotece standardowej do przestrzeni nazw std w pierwszej kolejności!

Ostrzeżenie

Unikaj używania dyrektyw (takich jak używanie przestrzeni nazw std;) na górze programu. Naruszają one powód, dla którego przestrzenie nazw zostały dodane w pierwszej kolejności.

więcej o używaniu oświadczeń (i o tym, jak używać ich odpowiedzialnie) omówimy w lekcji 6.12 — używanie oświadczeń.

2.9 — Wprowadzenie do preprocesora

indeks

2.7 — programy z wieloma plikami kodu

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.