Dlaczego wykorzystywane są nienazwane przestrzenie nazw i jakie są ich zalety?

242

Właśnie dołączyłem do nowego projektu oprogramowania C ++ i staram się zrozumieć projekt. Projekt często wykorzystuje nienazwane przestrzenie nazw. Na przykład coś takiego może wystąpić w pliku definicji klasy:

// newusertype.cc
namespace {
  const int SIZE_OF_ARRAY_X;
  const int SIZE_OF_ARRAY_Y;
  bool getState(userType*,otherUserType*);
}

newusertype::newusertype(...) {...

Jakie są względy projektowe, które mogą spowodować użycie nienazwanej przestrzeni nazw? Jakie są zalety i wady?

Scottie T.
źródło

Odpowiedzi:

189

Nienazwane przestrzenie nazw są narzędziem służącym do uczynienia jednostki tłumaczenia identyfikatora lokalną. Zachowują się tak, jakbyś wybrał unikalną nazwę na jednostkę tłumaczenia dla przestrzeni nazw:

namespace unique { /* empty */ }
using namespace unique;
namespace unique { /* namespace body. stuff in here */ }

Dodatkowy krok przy użyciu pustego ciała jest ważny, więc możesz już odwoływać się w treści przestrzeni nazw do identyfikatorów takich jak ::namete zdefiniowane w tej przestrzeni nazw, ponieważ już miała miejsce dyrektywa using.

Oznacza to, że możesz mieć wywoływane bezpłatne funkcje (na przykład), helpktóre mogą istnieć w wielu jednostkach tłumaczeniowych i nie będą się one kolidować w czasie łączenia. Efekt jest prawie identyczny z użyciem staticsłowa kluczowego użytego w C, które można umieścić w deklaracji identyfikatorów. Nienazwane przestrzenie nazw są doskonałą alternatywą, ponieważ można nawet ustawić lokalną jednostkę tłumaczenia typów.

namespace { int a1; }
static int a2;

Oba asą lokalnymi jednostkami tłumaczeniowymi i nie kolidują w czasie linku. Różnica polega jednak na tym, że a1w anonimowej przestrzeni nazw uzyskuje się unikalną nazwę.

Przeczytaj doskonały artykuł w comeau-computing Dlaczego zamiast nazwy statycznej używana jest nienazwana przestrzeń nazw? ( Lustro Archive.org ).

Johannes Schaub - litb
źródło
Wyjaśniasz związek z static. Czy możesz również porównać z __attribute__ ((visibility ("hidden")))?
phinz
74

Posiadanie czegoś w anonimowej przestrzeni nazw oznacza, że ​​jest lokalny dla tej jednostki tłumaczeniowej (plik .cpp i wszystkie zawarte w nim elementy), oznacza to, że jeśli inny symbol o tej samej nazwie zostanie zdefiniowany gdzie indziej, nie będzie naruszenia reguły jednej definicji (ODR).

Jest to taki sam sposób, jak w przypadku C posiadania statycznej zmiennej globalnej lub funkcji statycznej, ale może być również używany do definicji klas (i powinien być używany raczej niż staticw C ++).

Wszystkie anonimowe przestrzenie nazw w tym samym pliku są traktowane jak ta sama przestrzeń nazw, a wszystkie anonimowe przestrzenie nazw w różnych plikach są odrębne. Anonimowa przestrzeń nazw to odpowiednik:

namespace __unique_compiler_generated_identifer0x42 {
    ...
}
using namespace __unique_compiler_generated_identifer0x42;
Motti
źródło
14

Nienazwana przestrzeń nazw ogranicza dostęp klasy, zmiennej, funkcji i obiektów do pliku, w którym jest zdefiniowana. Funkcjonalność przestrzeni nazw bez nazwy jest podobna do staticsłowa kluczowego w C / C ++.
staticsłowo kluczowe ogranicza dostęp do zmiennej globalnej i funkcji do pliku, w którym są zdefiniowane.
Istnieje różnica między bezimienną przestrzenią nazw a staticsłowem kluczowym, z powodu której nienazwana przestrzeń nazw ma przewagę nad statyczną. staticSłowo kluczowe może być używane ze zmienną, funkcją i obiektami, ale nie z klasą zdefiniowaną przez użytkownika.
Na przykład:

static int x;  // Correct 

Ale,

static class xyz {/*Body of class*/} //Wrong
static structure {/*Body of structure*/} //Wrong

Ale to samo może być możliwe w przypadku nienazwanej przestrzeni nazw. Na przykład,

 namespace {
           class xyz {/*Body of class*/}
           static structure {/*Body of structure*/}
  } //Correct
Sachin
źródło
13

Oprócz innych odpowiedzi na to pytanie, użycie anonimowej przestrzeni nazw może również poprawić wydajność. Ponieważ symbole w przestrzeni nazw nie wymagają żadnego zewnętrznego powiązania, kompilator może swobodnie przeprowadzać agresywną optymalizację kodu w przestrzeni nazw. Na przykład funkcję, która jest wywoływana wielokrotnie w pętli, można wstawić bez wpływu na rozmiar kodu.

Na przykład w moim systemie następujący kod zajmuje około 70% czasu działania, jeśli używana jest anonimowa przestrzeń nazw (x86-64 gcc-4.6.3 i -O2; zwróć uwagę, że dodatkowy kod w add_val powoduje, że kompilator nie chce uwzględniać dwa razy).

#include <iostream>

namespace {
  double a;
  void b(double x)
  {
    a -= x;
  }
  void add_val(double x)
  {
    a += x;
    if(x==0.01) b(0);
    if(x==0.02) b(0.6);
    if(x==0.03) b(-0.1);
    if(x==0.04) b(0.4);
  }
}

int main()
{
  a = 0;
  for(int i=0; i<1000000000; ++i)
    {
      add_val(i*1e-10);
    }
  std::cout << a << '\n';
  return 0;
}
xioxox
źródło
5
Zbyt dobrze, aby mogło być prawdziwe - próbowałem tego segmentu na gcc 4-1-2, używając optymalizacji O3, z instrukcją przestrzeni nazw i bez niej: -> Mam ten sam czas (3 sekundy, z -O3 i 4 sekundy z -O3)
Theo
2
Ten kod był celowo skomplikowany, aby przekonać kompilator, aby nie wstawiał b i nie dodawał_wartości do main. Optymalizacja O3 wykorzystuje wiele wstawiania, niezależnie od kosztu wzdęcia. Nadal jednak istnieją prawdopodobne funkcje, w których O3 nie wstawiłoby wartości add_val. Możesz spróbować uczynić add_val bardziej złożonym lub wywoływać go wielokrotnie z main w różnych okolicznościach.
xioxox
5
@Daniel: czego mi brakuje? jak czytałeś, powiedziałeś, że porównałeś się -O3do siebie, a potem powiedziałeś, że 3 do 4 sekund to „ten sam czas”. żadne z nich nie ma sensu. Podejrzewam, że prawdziwe byłoby wyjaśnienie, ale co to jest?
underscore_d
@underscore_d W obu przypadkach użyto odpowiedzi -O2, a nie -O3. Różne poziomy optymalizacji mogą zachowywać się inaczej. Ponadto różne wersje kompilatora mogą zachowywać się inaczej (odpowiedź może się przedawnić, to znaczy)
Paul Stelian,
1
@PaulStelian Wiem o tym, ale wydaje się całkiem jasne, że odpowiadałem nie na odpowiedź xioxox, ale raczej na komentarz Theo (choć jego imię się zmieniło lub jakoś się pomieszałem)
underscore_d
12

Przykład pokazuje, że osoby w projekcie, do którego dołączyłeś, nie rozumieją anonimowych przestrzeni nazw :)

namespace {
    const int SIZE_OF_ARRAY_X;
    const int SIZE_OF_ARRAY_Y;

Nie muszą one znajdować się w anonimowej przestrzeni nazw, ponieważ constobiekt ma już statyczne powiązanie i dlatego nie może kolidować z identyfikatorami o tej samej nazwie w innej jednostce tłumaczeniowej.

    bool getState(userType*,otherUserType*);
}

I to jest właściwie pesymizacja: getState()ma zewnętrzne powiązanie. Zwykle lepiej jest preferować łączenie statyczne, ponieważ nie zanieczyszcza to tablicy symboli. Lepiej pisać

static bool getState(/*...*/);

tutaj. Wpadłem w tę samą pułapkę (w standardzie jest sformułowanie sugerujące, że statystyka plików jest w pewnym sensie przestarzała na rzecz anonimowych przestrzeni nazw), ale pracując w dużym projekcie C ++, takim jak KDE, masz wielu ludzi, którzy odwracają głowę we właściwy sposób jeszcze raz :)

Marc Mutz - mmutz
źródło
10
Ponieważ nienazwane przestrzenie nazw c ++ 11 mają wewnętrzne powiązanie (sekcja 3.5 w standardzie lub en.cppreference.com/w/cpp/language/namespace#Unnamed_namespaces )
Emile Vrijdags
11
„To nie muszą być w anonimowej przestrzeni nazw” Technicznie rzecz biorąc, na pewno - ale nadal, to nie boli, aby umieścić je w jednym, jako wizualne przypomnienie o ich semantyki i uczynić go (nawet więcej) trywialny do usunięcia constNess w razie potrzeby później. Wątpię, żeby to oznaczało, że zespół PO „niczego nie rozumie”! Ponadto, jak wspomniano, nieco więcej o funkcjach w anonimowych przestrzeniach nazw z zewnętrznym powiązaniem jest niepoprawny w C ++ 11. W moim rozumieniu naprawiono problem z szablonowymi argumentami, które wcześniej wymagały zewnętrznego połączenia, więc mogły pozwolić, aby nienazwane przestrzenie nazw (mogące zawierać argumenty szablonu) miały wewnętrzne połączenie.
underscore_d
11

Anonimowa przestrzeń nazw udostępnia zamknięte zmienne, funkcje, klasy itp. Tylko w tym pliku. W twoim przykładzie jest to sposób na uniknięcie zmiennych globalnych. Nie ma różnicy wydajności w czasie wykonywania ani w czasie kompilacji.

Nie ma tak dużej przewagi czy wady oprócz „czy chcę, aby ta zmienna, funkcja, klasa itp. Była publiczna czy prywatna?”

Max Lybbert
źródło
2
Mogą występować różnice w wydajności - zobacz moją odpowiedź tutaj. Pozwala to kompilatorowi na lepszą optymalizację kodu.
xioxox
2
Masz rację; przynajmniej w dzisiejszym C ++. Jednak C ++ 98 / C ++ 03 wymagało zewnętrznego powiązania, aby można go było wykorzystać jako argumenty szablonu. Ponieważ rzeczy w anonimowych przestrzeniach nazw są dostępne jako argumenty szablonów, miałyby one zewnętrzne powiązanie (przynajmniej w wersji wcześniejszej niż C ++ 11), nawet gdyby nie było możliwości odwołania się do nich spoza pliku. Myślę, że mogła istnieć jakaś zdolność do kruszenia tego, ponieważ standard wymaga tylko, aby rzeczy działały tak, jakby reguły były egzekwowane; i czasami można to zrobić bez prawdziwego egzekwowania zasad.
Max Lybbert