Wewnętrzne przestrzenie nazw są funkcją wersjonowania biblioteki podobną do wersji symboli , ale zaimplementowaną wyłącznie na poziomie C ++ 11 (tj. Międzyplatformowym) zamiast być cechą określonego binarnego formatu wykonywalnego (tj. Specyficznego dla platformy).
Jest to mechanizm, dzięki któremu autor biblioteki może sprawić, aby zagnieżdżona przestrzeń nazw wyglądała i działała tak, jakby wszystkie jej deklaracje znajdowały się w otaczającej przestrzeni nazw (wbudowane przestrzenie nazw można zagnieżdżać, dzięki czemu nazwy „bardziej zagnieżdżone” przenikają aż do pierwszego -inline przestrzeń nazw i wygląda i działa tak, jakby ich deklaracje znajdowały się również w jednej z przestrzeni nazw pomiędzy nimi).
Jako przykład rozważ implementację STL vector
. Gdybyśmy mieli wbudowane przestrzenie nazw od początku C ++, to w C ++ 98 nagłówek <vector>
mógłby wyglądać tak:
namespace std {
#if __cplusplus < 1997L // pre-standard C++
inline
#endif
namespace pre_cxx_1997 {
template <class T> __vector_impl; // implementation class
template <class T> // e.g. w/o allocator argument
class vector : __vector_impl<T> { // private inheritance
// ...
};
}
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
# if __cplusplus == 1997L // C++98/03
inline
# endif
namespace cxx_1997 {
// std::vector now has an allocator argument
template <class T, class Alloc=std::allocator<T> >
class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
// ...
};
// and vector<bool> is special:
template <class Alloc=std::allocator<bool> >
class vector<bool> {
// ...
};
};
#endif // C++98/03 or later
} // namespace std
W zależności od wartości wybierana jest __cplusplus
jedna lub druga vector
implementacja. Jeśli codebase został napisany w pre-C ++ 98 razy, a okaże się, że C ++ 98 wersja vector
jest przyczyną kłopotów dla ciebie, kiedy uaktualnić kompilator, „wszystkie”, co musisz zrobić, to znaleźć odniesienia do std::vector
w twoją bazę kodów i zastąp je std::pre_cxx_1997::vector
.
Przyjdź następny standard, a sprzedawca STL po prostu ponownie powtarza procedurę, wprowadzając nową przestrzeń nazw std::vector
z emplace_back
obsługą (która wymaga C ++ 11) i wstawiając tę iff __cplusplus == 201103L
.
OK, więc dlaczego potrzebuję do tego nowej funkcji językowej? Mogę już wykonać następujące czynności, aby uzyskać ten sam efekt, nie?
namespace std {
namespace pre_cxx_1997 {
// ...
}
#if __cplusplus < 1997L // pre-standard C++
using namespace pre_cxx_1997;
#endif
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
namespace cxx_1997 {
// ...
};
# if __cplusplus == 1997L // C++98/03
using namespace cxx_1997;
# endif
#endif // C++98/03 or later
} // namespace std
W zależności od wartości __cplusplus
otrzymuję jedną z tych implementacji.
I miałbyś prawie rację.
Rozważ następujący poprawny kod użytkownika C ++ 98 (dozwolone było w pełni specjalizowanie szablonów, które już istnieją w przestrzeni nazw std
w C ++ 98):
// I don't trust my STL vendor to do this optimisation, so force these
// specializations myself:
namespace std {
template <>
class vector<MyType> : my_special_vector<MyType> {
// ...
};
template <>
class vector<MyOtherType> : my_special_vector<MyOtherType> {
// ...
};
// ...etc...
} // namespace std
Jest to całkowicie poprawny kod, w którym użytkownik dostarcza własną implementację wektora dla zestawu typów, w którym najwyraźniej zna bardziej wydajną implementację niż ta znaleziona w (jej kopii) STL.
Ale : Specjalizując się w szablonie, musisz to zrobić w przestrzeni nazw, w której został zadeklarowany. Standard mówi, że vector
jest zadeklarowany w przestrzeni nazw std
, więc to właśnie tam użytkownik słusznie spodziewa się specjalizacji typu.
Ten kod działa z niewersjonowaną przestrzenią nazw std
lub z wbudowaną funkcją przestrzeni nazw C ++ 11, ale nie z zastosowaną sztuczką wersjonowania using namespace <nested>
, ponieważ ujawnia szczegół implementacji, że prawdziwa przestrzeń nazw, w której vector
została zdefiniowana, nie była std
bezpośrednio.
Istnieją inne dziury, przez które można wykryć zagnieżdżoną przestrzeń nazw (patrz komentarze poniżej), ale wbudowane przestrzenie nazw zatapiają je wszystkie. I to wszystko. Niezwykle przydatne w przyszłości, ale AFAIK Standard nie określa wbudowanych nazw przestrzeni nazw dla swojej własnej biblioteki standardowej (choć chciałbym, żeby mi się to nie udało), więc można jej używać tylko do bibliotek stron trzecich, a nie sam standard (chyba że dostawcy kompilatora uzgodnią schemat nazewnictwa).
using namespace V99;
nie działa w przykładzie Stroustrupa.std::cxx_11
. Nie każdy kompilator zawsze zaimplementuje wszystkie stare wersje bibliotek standardowych, mimo że w tej chwili kuszące jest myślenie, że byłoby bardzo małe obciążenie wymagać od istniejących implementacji pozostawienia starej wersji, gdy dodają nową, ponieważ w rzeczywistości wszystkie one i tak są. Podejrzewam, że to, co standard mógł pożytecznie zrobić, stało się opcjonalne, ale ze standardową nazwą, jeśli istnieje.using namespace A
w przestrzeni nazw B sprawia, że nazwy w przestrzeni nazw B ukrywają nazwy w przestrzeni nazw A, jeśli szukaszB::name
- nie w przypadku wbudowanych przestrzeni nazw).ifdef
s do pełnej implementacji wektorowej? Wszystkie implementacje byłyby w jednej przestrzeni nazw, ale tylko jedna z nich zostanie zdefiniowana po przetwarzaniuusing
słowo kluczowe).http://www.stroustrup.com/C++11FAQ.html#inline-namespace (dokument napisany i prowadzony przez Bjarne Stroustrup, który według ciebie powinien być świadomy większości motywacji dla większości funkcji C ++ 11. )
Zgodnie z tym ma umożliwić wersjonowanie w celu zapewnienia zgodności wstecznej. Definiujesz wiele wewnętrznych przestrzeni nazw i tworzysz najnowszą
inline
. W każdym razie domyślny dla osób, którym nie zależy na wersjonowaniu. Przypuszczam, że najnowsza może być przyszłą lub najnowocześniejszą wersją, która nie jest jeszcze domyślna.Podany przykład to:
Nie rozumiem od razu, dlaczego nie umieściłeś
using namespace V99;
w przestrzeni nazwMine
, ale nie muszę do końca rozumieć przypadku użycia, aby wziąć słowo Bjarne na motywację komitetu.źródło
f(1)
wersja byłaby wywoływana z wbudowanejV99
przestrzeni nazw?using namespace Mine;
, aMine
przestrzeń nazw zawiera wszystko z wbudowanej przestrzeni nazwMine::V99
.inline
z plikuV99.h
w dołączonej wersjiV100.h
. Oczywiście modyfikujesz równieżMine.h
w tym samym czasie, aby dodać dodatkowe dołączenie.Mine.h
jest częścią biblioteki, a nie częścią kodu klienta.V100.h
, instalują bibliotekę o nazwie „Mój”. Istnieją 3 pliki nagłówkowe w wersji 99 „moje” -Mine.h
,V98.h
iV99.h
. Są to 4 pliki nagłówkowe w wersji 100 „moje” -Mine.h
,V98.h
,V99.h
iV100.h
. Układ plików nagłówkowych jest szczegółem implementacji, który nie ma znaczenia dla użytkowników. Jeśli odkryją jakiś problem ze zgodnością, co oznacza, że muszą używać konkretnieMine::V98::f
całego lub całego kodu, mogą mieszać wywołaniaMine::V98::f
ze starego kodu ze wywołaniamiMine::f
w nowo napisanym kodzie.Mine
, zamiast specjalizować się wMine::V99
lubMine::V98
.Oprócz wszystkich innych odpowiedzi.
Wewnętrzna przestrzeń nazw może być używana do kodowania informacji ABI lub wersji funkcji w symbolach. Z tego powodu są one używane do zapewnienia wstecznej kompatybilności ABI. Wbudowane przestrzenie nazw umożliwiają wstrzykiwanie informacji do zniekształconej nazwy (ABI) bez zmiany interfejsu API, ponieważ wpływają one tylko na nazwę symbolu linkera.
Rozważ ten przykład:
Załóżmy, że piszesz funkcję,
Foo
która odwołuje się do powiedzenia obiektubar
i nic nie zwraca.Powiedz w main.cpp
Jeśli sprawdzisz nazwę swojego symbolu dla tego pliku po skompilowaniu go w obiekt.
Teraz może to być
bar
zdefiniowane jako:W zależności od typu kompilacji
bar
może odnosić się do dwóch różnych typów / układów z tymi samymi symbolami linkera.Aby temu zapobiec, zawijamy naszą strukturę
bar
do wbudowanej przestrzeni nazw, gdzie w zależności od typu Kompilacji symbol linkerabar
będzie inny.Więc moglibyśmy napisać:
Teraz, gdy spojrzysz na plik obiektu każdego obiektu, zbudujesz jeden za pomocą wydania, a drugi z flagą debugowania. Przekonasz się, że symbole linkera zawierają również wbudowaną nazwę przestrzeni nazw. W tym przypadku
Zwróć uwagę na obecność
rel
idbg
w nazwach symboli.Teraz, jeśli spróbujesz połączyć debugowanie z trybem zwalniania lub odwrotnie, otrzymasz błąd linkera jako sprzeczny z błędem środowiska uruchomieniowego.
źródło
Właściwie odkryłem inne zastosowanie dla wbudowanych przestrzeni nazw.
Dzięki Qt zyskujesz dodatkowe, miłe funkcje
Q_ENUM_NS
, które z kolei wymagają, aby otaczająca przestrzeń nazw zawierała meta obiekt, który jest zadeklarowany za pomocąQ_NAMESPACE
. JednakQ_ENUM_NS
aby działać, musi być odpowiedniQ_NAMESPACE
w tym samym pliku ⁽¹⁾. I może być tylko jeden, lub wystąpią błędy definicji duplikatu. To oznacza, że wszystkie twoje wyliczenia muszą znajdować się w tym samym nagłówku. FujLub ... możesz użyć wbudowanych przestrzeni nazw. Ukrywanie wyliczeń w
inline namespace
metodzie powoduje, że meta-obiekty mają różne zniekształcone nazwy, podczas gdy szukanie użytkowników takich jak dodatkowa przestrzeń nazw nie istnieje ⁽²⁾.Są więc przydatne do dzielenia rzeczy na wiele pod-nazw, które wszystkie wyglądają jak jedna przestrzeń nazw, jeśli z jakiegoś powodu musisz to zrobić. Oczywiście jest to podobne do pisania
using namespace inner
w zewnętrznej przestrzeni nazw, ale bez naruszenia DRY w przypadku dwukrotnego pisania nazwy wewnętrznej przestrzeni nazw.To jest naprawdę gorsze niż to; musi być w tym samym zestawie nawiasów klamrowych.
Chyba że spróbujesz uzyskać dostęp do meta-obiektu bez jego pełnego zakwalifikowania, ale meta-obiekt prawie nigdy nie jest używany bezpośrednio.
źródło
Tak więc, aby podsumować główne punkty,
using namespace v99
ainline namespace
nie były takie same, były było obejście bibliotek wersję przed dedykowanym słowa kluczowego (inline) został wprowadzony w C ++ 11, który Naprawiono problemy związane z użyciemusing
, zapewniając jednocześnie taką samą funkcjonalność wersjonowania. Używanieusing namespace
używane do powodowania problemów z ADL (chociaż ADL wydaje się teraz podążać zausing
dyrektywami), a poza-specjalizacja klasy / funkcji biblioteki itp. Przez użytkownika nie działałaby, gdyby została wykonana poza prawdziwą przestrzenią nazw (której nazwa użytkownik nie wiedziałby i nie powinien wiedzieć, tj. użytkownik musiałby użyć B :: abi_v2 :: zamiast tylko B :: do rozwiązania specjalizacji).Wyświetli się ostrzeżenie dotyczące analizy statycznej
first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. Ale jeśli utworzysz przestrzeń nazw A w linii, kompilator poprawnie rozwiąże specjalizację. Chociaż w przypadku rozszerzeń C ++ 11 problem zniknął.Definicje poza linią nie są rozwiązywane podczas używania
using
; należy je zadeklarować w zagnieżdżonym / nie zagnieżdżonym bloku przestrzeni nazw rozszerzeń (co oznacza, że użytkownik musi ponownie znać wersję ABI, jeśli z jakiegokolwiek powodu zezwolono im na zapewnienie własnej implementacji funkcji).Problem znika, gdy B jest wbudowany.
Inne funkcjonalne
inline
przestrzenie nazw mają na celu umożliwienie autorowi biblioteki dostarczenia przezroczystej aktualizacji biblioteki 1) bez zmuszania użytkownika do zmiany kodu z nową nazwą przestrzeni nazw i 2) zapobiegania brakowi szczegółowości oraz 3) zapewniania abstrakcji szczegółów niezwiązanych z API, podczas gdy 4) daje taką samą korzystną diagnostykę i zachowanie linkera, jaką zapewniłoby użycie nieliniowej przestrzeni nazw. Powiedzmy, że używasz biblioteki:Pozwala użytkownikowi dzwonić
library::foo
bez konieczności znajomości lub dołączenia wersji ABI do dokumentacji, która wygląda na czystszą. Używanielibrary::abiverison129389123::foo
wyglądałoby na brudne.Po dokonaniu aktualizacji
foo
, tj. Dodaniu nowego elementu do klasy, nie wpłynie to na istniejące programy na poziomie interfejsu API, ponieważ nie będą one już korzystać z tego elementu ORAZ zmiana nazwy wbudowanej przestrzeni nazw nic nie zmieni na poziomie interfejsu API ponieważlibrary::foo
nadal będzie działać.Jednak w przypadku programów, które się z nim łączą, ponieważ wbudowana nazwa przestrzeni nazw jest zniekształcona w nazwach symboli, tak jak zwykła przestrzeń nazw, zmiana nie będzie przezroczysta dla linkera. Dlatego jeśli aplikacja nie zostanie ponownie skompilowana, ale zostanie połączona z nową wersją biblioteki,
abi_v1
wyświetli się błąd, który nie został znaleziony, zamiast łączenia się, a następnie spowodowania tajemniczego błędu logicznego w czasie wykonywania z powodu niezgodności ABI. Dodanie nowego elementu spowoduje zgodność ABI ze względu na zmianę definicji typu, nawet jeśli nie wpłynie to na program w czasie kompilacji (poziom API).W tym scenariuszu:
Podobnie jak w przypadku korzystania z 2 nieliniowych przestrzeni nazw, umożliwia połączenie nowej wersji biblioteki bez konieczności ponownej kompilacji aplikacji, ponieważ
abi_v1
zostanie ona zniekształcona w jednym z globalnych symboli i użyje poprawnej (starej) definicji typu. Ponowna kompilacja aplikacji spowodowałaby jednak rozpoznanie referencjilibrary::abi_v2
.Używanie
using namespace
jest mniej funkcjonalne niż używanieinline
(ponieważ definicje poza linią nie rozwiązują), ale zapewnia te same 4 zalety, co powyżej. Ale prawdziwe pytanie brzmi: po co nadal stosować obejście, skoro istnieje teraz specjalne słowo kluczowe do tego celu. Jest to lepsza praktyka, mniej gadatliwa (trzeba zmienić 1 linię kodu zamiast 2) i wyjaśnia intencję.źródło