2012-06-13 12 views
260

C++ 11 zezwala na inline namespace s, której wszystkie elementy są również automatycznie umieszczane w obudowie namespace. Nie mogę wymyślić żadnego użytecznego zastosowania tego - czy ktoś może podać krótki, zwięzły przykład sytuacji, w której potrzebne jest inline namespace i gdzie jest to najbardziej idiomatyczne rozwiązanie?Do czego służą wbudowane przestrzenie nazw?

(Również, to nie jest dla mnie jasne, co się dzieje, gdy namespace deklaruje inline w jednym, ale nie wszystkie deklaracje, które mogą żyć w różnych plikach. Czy to nie prosi o kłopoty?)

Odpowiedz

259

Przestrzenie nazw są funkcją wersjonowania bibliotek, podobną do symbol versioning, ale są implementowane wyłącznie na poziomie C++ 11 (tj. Na wielu platformach), zamiast być cechą określonego formatu binarnego (np. .

Jest to mechanizm, dzięki któremu autor biblioteki może sprawić, że zagnieżdżona przestrzeń nazw wygląda i działa tak, jakby wszystkie jej deklaracje znajdowały się w otaczającym obszarze nazw (wbudowane przestrzenie nazw mogą być zagnieżdżane, więc nazwy "bardziej zagnieżdżone" przenikają całą drogę do pierwszej nieliniowej przestrzeni nazw i wyglądają tak, jakby ich deklaracje znajdowały się w przestrzeni nazw między nimi).

Jako przykład rozważmy implementację STL vector. Gdybyśmy mieli nazw wbudowanych od początku C++, to w C++ 98 nagłówek <vector> mógł 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 

zależności od wartości __cplusplus, albo jedno albo drugie realizacja vector jest wybrany. Jeśli twoja baza kodowa została napisana w C++ 98 razy i okazało się, że wersja C++ 98 z vector powoduje problemy podczas aktualizacji kompilatora, "wszystko", co musisz zrobić, to znaleźć odniesienia do std::vector w Twojej bazie kodów i zamień je na std::pre_cxx_1997::vector.

Przyjdź następnego poziomu, a sprzedawca STL tylko powtarza procedurę ponownie, wprowadzając nową nazw dla std::vector z emplace_back wsparcia (co wymaga C++ 11) i inline że jeden iff __cplusplus == 201103L.

OK, 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 

zależności od wartości __cplusplus, mam jeden lub drugi z wdrożeń.

I będziesz prawie poprawny.

Rozważmy następujący ważny C++ 98 kod użytkownika (nie wolno było w pełni specjalizują się szablony, które żyją w przestrzeni nazw std w C++ 98 już):

// 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 

To jest całkowicie poprawny kod, w którym użytkownik dostarcza własną implementację wektora dla zbioru czcionek, w którym pozornie zna bardziej wydajną implementację niż ta, którą znaleziono (jej kopię) STL.

Ale: Kiedy specjalizujący szablonu, trzeba to zrobić w przestrzeni nazw zostało zadeklarowane w standardowe mówi, że vector jest zadeklarowana w przestrzeni nazw std, więc to, gdzie użytkownik słusznie spodziewa się specjalizować typ..

Ten kod działa z non-wersjami nazw std lub z funkcją przestrzeni nazw C++ 11 inline, ale nie trick wersjami, które kiedyś using namespace <nested>, ponieważ naraża szczegółów implementacji, że prawdziwa przestrzeń nazw, w której był vector zdefiniowany bezpośrednio nie był std.

Istnieją inne dziury, dzięki którym można wykryć zagnieżdżone przestrzenie nazw (zobacz komentarze poniżej), ale wstawione przestrzenie nazw wtyczek je wszystkie. I to wszystko. Niezwykle przydatny w przyszłości, ale AFAIK Standard nie określa wbudowanych nazw przestrzeni nazw dla swojej własnej standardowej biblioteki (chciałbym jednak, aby udowodniono, że to nieprawda), więc można go używać tylko w bibliotekach innych firm, nie sam standard (chyba że producenci kompilatorów zgadzają się na schemat nazewnictwa).

+13

+1 za wyjaśnienie, dlaczego 'using namespace V99;' nie działa w przykładzie Stroustrupa. –

+2

Przypuszczam, że wersjonowanie w standardowych bibliotekach w standardowy sposób jest ograniczone w porównaniu z wersjami innych firm.Jeśli wektor 'C++ 21 'nie jest dla mnie dobry, a ja potrzebuję wektora' C++ 11', to może być to spowodowane zmianą w C++ 21. Ale może to być spowodowane szczegółami implementacji, na których nie powinienem był polegać. Standard nie może wymagać, aby każda implementacja C++ 21 zawierała 'std :: cxx_11 :: vector', która jest zgodna z błędem z jakimkolwiek przeszłym' std :: vector' z tego samego dostawcy. Biblioteki stron trzecich mogą to zrobić, jeśli uważają, że warto. –

+3

I podobnie, jeśli zacznę od zupełnie nowej implementacji C++ 21 od podstaw, to nie chcę obciążać implementacją wielu starych bzdur w 'std :: cxx_11'. Nie każdy kompilator zawsze będzie implementował wszystkie stare wersje standardowych bibliotek, nawet jeśli kusi w tym momencie, że będzie bardzo małym obciążeniem wymagać, aby istniejące implementacje pozostały w starym, gdy dodadzą nowe, ponieważ w rzeczywistości wszystkie i tak są. Przypuszczam, że to, co standard mógł zrobić z pożytkiem, stało się opcjonalne, ale ze standardową nazwą, jeśli jest obecna. –

52

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (dokument napisany i utrzymywany przez Bjarne Stroustrup, który, twoim zdaniem, powinien być świadomy większości motywacji dla większości funkcji C++ 11).

Zgodnie z tym warunkiem jest zapewnienie kompatybilności z poprzednimi wersjami. Definiujesz wiele wewnętrznych przestrzeni nazw i tworzysz najnowszy inline. Lub, tak czy inaczej, domyślny dla osób, które nie dbają o wersjonowanie. Przypuszczam, że najnowszą z nich może być nowa lub nowoczesna wersja, która nie jest jeszcze domyślna.

podanym przykładzie jest:

// file V99.h: 
inline namespace V99 { 
    void f(int); // does something better than the V98 version 
    void f(double); // new feature 
    // ... 
} 

// file V98.h: 
namespace V98 { 
    void f(int); // does something 
    // ... 
} 

// file Mine.h: 
namespace Mine { 
#include "V99.h" 
#include "V98.h" 
} 

#include "Mine.h" 
using namespace Mine; 
// ... 
V98::f(1); // old version 
V99::f(1); // new version 
f(1);  // default version 

ja nie od razu zrozumieć, dlaczego nie umieścić using namespace V99; wewnątrz przestrzeni nazw Mine, ale nie mam do końca zrozumieć przypadków użycia w celu podjęcia Słowo Bjarne o tym na motywację komisji.

+0

Więc w rzeczywistości ostatnia wersja 'f (1)' zostanie wywołana z wbudowanej przestrzeni nazw 'V99'? –

+1

@EitanT: tak, ponieważ globalna przestrzeń nazw ma 'using namespace Mine;', a przestrzeń nazw 'Mine' zawiera wszystko od wbudowanej przestrzeni nazw' Mine :: V99'. –

+0

ale aby to działało, musiałbym dodać 'inline' do pliku V99.h. Co się stanie, gdy pojawi się plik V100.h, deklarujący "wbudowaną przestrzeń nazw V100", a ja też ją uwzględnię w "Mine"? Którego 'f (int)' jest wywoływane przez 'f (1)'? – Walter