Nienazwane / anonimowe przestrzenie nazw a funkcje statyczne

507

Cechą C ++ jest możliwość tworzenia nienazwanych (anonimowych) przestrzeni nazw, takich jak:

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

Można by pomyśleć, że taka funkcja byłaby bezużyteczna - ponieważ nie można podać nazwy przestrzeni nazw, nie można uzyskać do niej dostępu z zewnątrz. Ale te nienazwane przestrzenie nazw dostępne w pliku, w którym zostały utworzone, tak jakbyś miał dla nich niejawną klauzulę use.

Moje pytanie brzmi: dlaczego lub kiedy byłoby to lepsze niż używanie funkcji statycznych? Czy są to zasadniczo dwa sposoby robienia dokładnie tego samego?

Head Geek
źródło
13
W C ++ 11 użycie staticw tym kontekście było nieaktualne ; chociaż nienazwana przestrzeń nazw jest doskonałą alternatywą dlastatic , istnieją przypadki, w których zawodzi, gdy staticprzychodzi na ratunek .
legends2k

Odpowiedzi:

332

Standard C ++ czyta się w sekcji 7.3.1.1 Nienazwane przestrzenie nazw, akapit 2:

Zastosowanie słowa kluczowego static jest przestarzałe w przypadku deklarowania obiektów w zakresie przestrzeni nazw, przestrzeń nazw bez nazwy stanowi lepszą alternatywę.

Statyczne dotyczy tylko nazw obiektów, funkcji i anonimowych związków, a nie deklaracji typów.

Edytować:

Decyzja o wycofaniu tego użycia statycznego słowa kluczowego (wpływa na widoczność deklaracji zmiennej w jednostce tłumaczeniowej) została cofnięta ( odwołanie ). W tym przypadku użycie statycznej lub nienazwanej przestrzeni nazw powraca do bycia zasadniczo dwoma sposobami wykonania dokładnie tego samego. Aby uzyskać więcej dyskusji, zobacz to SO pytanie.

Nienazwane przestrzenie nazw mają tę zaletę, że pozwalają definiować typy jednostek lokalnych tłumaczących. Zobacz to SO pytanie, aby uzyskać więcej informacji.

Podziękowania należą się Mike'owi Percy'emu za zwrócenie mi na to uwagi.

Łukasz
źródło
39
Head Geek pyta o statyczne słowo kluczowe używane tylko przeciwko funkcjom. Statyczne słowo kluczowe zastosowane do encji zadeklarowanej w zakresie przestrzeni nazw określa jej wewnętrzne powiązanie. Podmiot zadeklarowany w anonimowej przestrzeni nazw ma zewnętrzny link (C ++ / 3.5), jednak gwarantuje, że będzie żył w unikalnie nazwanym zakresie. Ta anonimowość nienazwanej przestrzeni nazw skutecznie ukrywa jej deklarację, czyniąc ją dostępną tylko z jednostki tłumaczeniowej. Ten ostatni działa skutecznie w taki sam sposób jak słowo kluczowe static.
mloskot,
5
jaka jest wada połączenia zewnętrznego? Czy może to wpłynąć na wstawianie?
Alex,
17
Ci z komitetu projektowego C ++, którzy twierdzili, że statyczne słowo kluczowe jest przestarzałe, prawdopodobnie nigdy nie pracowali z ogromnym kodem C w dużym świecie rzeczywistym ... (Natychmiast widzisz statyczne słowo kluczowe, ale nie anonimową przestrzeń nazw, jeśli zawiera wiele deklaracji z dużym komentarzem bloki.)
Calmarius
23
Ponieważ ta odpowiedź pojawia się w Google jako najlepszy wynik dla „anonimowej przestrzeni nazw c ++”, należy zauważyć, że użycie statycznego nie jest już przestarzałe. Aby uzyskać więcej informacji, zobacz stackoverflow.com/questions/4726570/... i open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 .
Michael Percy,
2
@ErikAronesty To brzmi źle. Czy masz powtarzalny przykład? Począwszy od C ++ 11 - a nawet wcześniej w niektórych kompilatorach - nienazwane namespaces domyślnie mają wewnętrzne powiązanie, więc nie powinno być różnicy. Wszelkie problemy, które mogły wcześniej wynikać z niewłaściwego sformułowania, zostały rozwiązane poprzez uczynienie tego wymaganiem w C ++ 11.
underscore_d
73

Umieszczenie metod w anonimowej przestrzeni nazw zapobiega przypadkowemu naruszeniu reguły One Definition , pozwalając nigdy nie martwić się nazwaniem metod pomocniczych tak samo, jak innymi metodami, do których można się połączyć.

I, jak zauważył Luke, anonimowe przestrzenie nazw są preferowane przez standard nad elementami statycznymi.

Hazzen
źródło
2
Miałem na myśli statyczne autonomiczne funkcje (tj. Funkcje o zasięgu pliku), a nie statyczne funkcje składowe. Statyczne samodzielne funkcje są bardzo podobne do funkcji w nienazwanej przestrzeni nazw, stąd pytanie.
Head Geek
2
Ach; Cóż, ODR nadal obowiązuje. Edytowano, aby usunąć akapit.
hazzen
jak dostaję, ODR dla funkcji statycznej nie działa, gdy jest zdefiniowany w nagłówku i ten nagłówek jest zawarty w więcej niż jednej jednostce tłumaczenia, prawda? w tym przypadku otrzymujesz wiele kopii tej samej funkcji
Andrij Tylychko
@Andy T: Tak naprawdę nie widzisz „wielu definicji” w przypadku dołączonego nagłówka. Zajmuje się tym Preprocesor. Chyba że zajdzie potrzeba zbadania wyników, które wygenerował preprocesor, który dla mnie wygląda raczej egzotycznie i rzadko. Dobrą praktyką jest również dołączanie „strażników” do plików nagłówkowych, takich jak: „#ifndef SOME_GUARD - #define SOME_GUARD ...”, co ma zapobiegać dwukrotnemu włączaniu przez preprocesora tego samego nagłówka.
Nikita Vorontsov,
@NikitaVorontsov strażnik może zapobiec włączeniu tego samego nagłówka do tej samej jednostki tłumaczeniowej, jednak pozwala na wiele definicji w różnych jednostkach tłumaczeniowych. Może to spowodować błąd linkera „wiele definicji” w dół linii.
Alex
37

Jest jeden przypadek krawędzi, w którym statyczny ma zaskakujący efekt (przynajmniej dla mnie). Norma C ++ 03 stwierdza w 14.6.4.2/1:

W przypadku wywołania funkcji, która zależy od parametru szablonu, jeśli nazwa funkcji jest identyfikatorem niekwalifikowanym, ale nie identyfikatorem szablonu , funkcje kandydujące można znaleźć przy użyciu zwykłych reguł wyszukiwania (3.4.1, 3.4.2), z wyjątkiem tego, że:

  • W tej części wyszukiwania, w której zastosowano wyszukiwanie bez kwalifikacji (3.4.1), można znaleźć tylko deklaracje funkcji z zewnętrznym powiązaniem z kontekstu definicji szablonu.
  • W części wyszukiwania używającej powiązanych przestrzeni nazw (3.4.2) można znaleźć tylko deklaracje funkcji z zewnętrznym powiązaniem znalezione w kontekście definicji szablonu lub kontekście tworzenia szablonu.

...

Poniższy kod zadzwoni foo(void*)i nie foo(S const &)tak, jak można się spodziewać.

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

Samo w sobie prawdopodobnie nie jest to wielka sprawa, ale podkreśla, że ​​dla w pełni zgodnego kompilatora C ++ (tj. Z obsługą export) staticsłowo kluczowe nadal będzie miało funkcjonalność, która nie będzie dostępna w żaden inny sposób.

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

Jedynym sposobem, aby upewnić się, że funkcja w naszym nienazwanym obszarze nazw nie zostanie znaleziona w szablonach korzystających z ADL, to zrobić to static .

Aktualizacja dla Modern C ++

Począwszy od C ++ '11, członkowie nienazwanej przestrzeni nazw mają niejawnie wewnętrzne powiązanie (3.5 / 4):

Nienazwana przestrzeń nazw lub przestrzeń nazw zadeklarowana bezpośrednio lub pośrednio w nienazwanej przestrzeni nazw ma wewnętrzne powiązanie.

Ale jednocześnie 14.6.4.2/1 został zaktualizowany, aby usunąć wzmiankę o powiązaniu (wzięte z C ++ '14):

W przypadku wywołania funkcji, w której wyrażenie postfiksowe jest nazwą zależną, funkcje kandydujące można znaleźć przy użyciu zwykłych reguł wyszukiwania (3.4.1, 3.4.2), z wyjątkiem tego, że:

  • W części wyszukiwania wykorzystującej wyszukiwanie niekwalifikowane (3.4.1) można znaleźć tylko deklaracje funkcji z kontekstu definicji szablonu.

  • W części wyszukiwania używającej powiązanych przestrzeni nazw (3.4.2) można znaleźć tylko deklaracje funkcji znalezione w kontekście definicji szablonu lub kontekście tworzenia szablonu.

W rezultacie ta szczególna różnica między statycznymi i nienazwanymi elementami przestrzeni nazw już nie istnieje.

Richard Corden
źródło
3
Czy słowo kluczowe eksportu nie powinno być martwe? Jedynymi kompilatorami obsługującymi „eksport” są kompilatory eksperymentalne i chyba że niespodzianki „eksport” nawet nie zostaną zaimplementowane w innych z powodu nieoczekiwanych efektów ubocznych (oprócz tego, że nie było to oczekiwane)
paercebal,
2
Zobacz artykuł Herb Suttera na temat podstrumienia
paercebal
3
Interfejs Edison Design Group (EDG) nie jest eksperymentalny. Jest to prawie na pewno najbardziej standardowa implementacja C ++ na świecie. Kompilator Intel C ++ wykorzystuje EDG.
Richard Corden,
1
Jaka funkcja C ++ nie ma „nieoczekiwanych efektów ubocznych”? W przypadku eksportu jest to, że nienazwana funkcja przestrzeni nazw zostanie znaleziona z innej JT - to znaczy tak, jakbyś bezpośrednio zawarł definicję szablonu. Byłoby bardziej zaskakujące, gdyby tak nie było!
Richard Corden,
Myślę, że masz tam literówkę - NS::Sżeby pracować, nie Smusisz być w środku namespace {}?
Eric,
12

Niedawno zacząłem zastępować statyczne słowa kluczowe anonimowymi przestrzeniami nazw w moim kodzie, ale od razu natknąłem się na problem polegający na tym, że zmienne w przestrzeni nazw nie były już dostępne do kontroli w moim debuggerze. Korzystałem z VC60, więc nie wiem, czy nie jest to problem z innymi debuggerami. Moim obejściem było zdefiniowanie przestrzeni nazw „modułu”, w której nadałem jej nazwę mojego pliku CPP.

Na przykład w moim pliku XmlUtil.cpp definiuję przestrzeń nazw XmlUtil_I { ... }dla wszystkich moich zmiennych modułu i funkcji. W ten sposób mogę zastosować XmlUtil_I::kwalifikację w debuggerze, aby uzyskać dostęp do zmiennych. W tym przypadku _Iodróżnia go od publicznej przestrzeni nazw, takiej jakXmlUtil której mogę chcieć użyć gdzie indziej.

Podejrzewam, że potencjalną wadą tego podejścia w porównaniu z prawdziwie anonimowym jest to, że ktoś może naruszyć pożądany zakres statyczny, używając kwalifikatora przestrzeni nazw w innych modułach. Nie wiem jednak, czy to poważny problem.

Evg
źródło
7
Zrobiłem to również, ale za pomocą #if DEBUG namespace BlahBlah_private { #else namespace { #endif, więc „przestrzeń nazw modułów” jest obecna tylko w kompilacjach debugowania, a prawdziwa anonimowa przestrzeń nazw jest używana w inny sposób. Byłoby miło, gdyby debuggery dawały dobry sposób na poradzenie sobie z tym. Doxygen też się tym myli.
Kristopher Johnson
4
nienazwana przestrzeń nazw nie jest tak naprawdę realnym zamiennikiem dla static. statyczny oznacza „tak naprawdę to nigdy nie łączy się z jednostką TU”. nienazwana przestrzeń nazw oznacza „nadal jest eksportowana jako nazwa losowa, na wypadek gdyby została wywołana z klasy nadrzędnej spoza TU” ...
Erik Aronesty
7

Używanie statycznego słowa kluczowego do tego celu jest przestarzałe w standardzie C ++ 98. Problem ze statycznym polega na tym, że nie dotyczy definicji typu. Jest to również przeciążone słowo kluczowe używane na różne sposoby w różnych kontekstach, więc nienazwane przestrzenie nazw nieco upraszczają sprawę.

Firas Assaad
źródło
1
Jeśli chcesz użyć typu tylko w jednej jednostce tłumaczeniowej, zadeklaruj go w pliku .cpp. I tak nie będzie dostępny z innych jednostek tłumaczeniowych.
Calmarius
4
Myślałbyś, prawda? Ale jeśli inna jednostka tłumacząca (= plik cpp) w tej samej aplikacji kiedykolwiek zadeklaruje typ o tej samej nazwie, masz problemy z trudnym debugowaniem :-). Na przykład mogą wystąpić sytuacje, w których vtable dla jednego z typów jest używany podczas wywoływania metod na drugim.
avl_sweden
1
Już nie przestarzałe. A def. Typów nie są eksportowane, więc nie ma to znaczenia. statyka jest przydatna w przypadku samodzielnych funkcji i zmiennych globalnych. nienazwane przestrzenie nazw są przydatne dla klas.
Erik Aronesty
6

Z doświadczenia zauważę tylko, że chociaż jest to sposób C ++ do umieszczania funkcji statycznych w anonimowej przestrzeni nazw, starsze kompilatory mogą czasem mieć z tym problemy. Obecnie pracuję z kilkoma kompilatorami dla naszych platform docelowych, a nowocześniejszy kompilator Linux jest w porządku, umieszczając funkcje w anonimowej przestrzeni nazw.

Ale starszy kompilator działający w systemie Solaris, do którego jesteśmy zobowiązani do nieokreślonej przyszłej wersji, czasami go zaakceptuje, a innym razem oznaczy go jako błąd. Błąd nie jest tym, co mnie martwi, ale to, co może robić, kiedy to zaakceptuje . Tak więc, dopóki nie przejdziemy na nowo, nadal używamy funkcji statycznych (zwykle o zasięgu klasowym), w których wolelibyśmy anonimową przestrzeń nazw.

Don Wakefield
źródło
3

Ponadto, jeśli używa się słowa kluczowego static na zmiennej takiej jak ten przykład:

namespace {
   static int flag;
}

Nie będzie go widać w pliku odwzorowania

Chris
źródło
7
Wtedy w ogóle nie potrzebujesz anonimowej przestrzeni nazw.
Calmarius
2

Różnica charakterystyczna dla kompilatora między anonimowymi przestrzeniami nazw a funkcjami statycznymi jest widoczna podczas kompilowania następującego kodu.

#include <iostream>

namespace
{
    void unreferenced()
    {
        std::cout << "Unreferenced";
    }

    void referenced()
    {
        std::cout << "Referenced";
    }
}

static void static_unreferenced()
{
    std::cout << "Unreferenced";
}

static void static_referenced()
{
    std::cout << "Referenced";
}

int main()
{
    referenced();
    static_referenced();
    return 0;
}

Kompilowanie tego kodu z VS 2017 (określenie flagi ostrzeżenia poziomu 4 / W4 w celu włączenia ostrzeżenia C4505: usunięto niereferencyjną funkcję lokalną ), a gcc 4.9 z opcją -Wunused-function lub -Wall flag pokazuje, że VS 2017 wygeneruje tylko ostrzeżenie dla nieużywana funkcja statyczna. gcc 4.9 i wyższe, a także clang 3.3 i wyższe, będą generować ostrzeżenia dla funkcji, do których nie ma odniesienia w przestrzeni nazw, a także ostrzeżenie dla nieużywanej funkcji statycznej.

Demo na żywo z gcc 4.9 i MSVC 2017

masrtis
źródło
2

Osobiście wolę funkcje statyczne niż bezimienne przestrzenie nazw z następujących powodów:

  • Z samej definicji funkcji jest oczywiste i jasne, że jest prywatna dla jednostki tłumaczącej, w której jest skompilowana. W przypadku bezimiennej przestrzeni nazw konieczne może być przewijanie i wyszukiwanie w celu sprawdzenia, czy funkcja znajduje się w przestrzeni nazw.

  • Funkcje w obszarach nazw mogą być traktowane jako zewnętrzne przez niektóre (starsze) kompilatory. W VS2017 są jeszcze zewnętrzni. Z tego powodu nawet jeśli funkcja znajduje się w bezimiennej przestrzeni nazw, nadal możesz chcieć oznaczyć ją jako statyczną.

  • Funkcje statyczne zachowują się bardzo podobnie w C lub C ++, podczas gdy bezimienne przestrzenie nazw są oczywiście tylko C ++. Bezimienne przestrzenie nazw dodają dodatkowy poziom wcięcia i nie podoba mi się to :)

Dlatego cieszę się, że użycie funkcji statycznych dla funkcji nie jest już przestarzałe .

Pavel P.
źródło
Funkcje w anonimowych przestrzeniach nazw powinny mieć zewnętrzne powiązanie. Są po prostu zniekształcone, aby były wyjątkowe. Tylko staticsłowo kluczowe faktycznie stosuje lokalne powiązanie z funkcją. Czy na pewno tylko szalony szaleniec rzeczywiście dodałby wcięcia w przestrzeni nazw?
Roflcopter4
0

Po zapoznaniu się z tą funkcją dopiero teraz, czytając twoje pytanie, mogę tylko spekulować. Wydaje się, że zapewnia to szereg korzyści w porównaniu ze zmienną statyczną na poziomie pliku:

  • Anonimowe przestrzenie nazw mogą być zagnieżdżone między sobą, zapewniając wiele poziomów ochrony, przed którymi nie mogą uciec symbole.
  • Kilka anonimowych przestrzeni nazw można umieścić w tym samym pliku źródłowym, tworząc w efekcie różne zakresy poziomu statycznego w tym samym pliku.

Chciałbym dowiedzieć się, czy ktoś używał anonimowych przestrzeni nazw w prawdziwym kodzie.

Komandor Jaeger
źródło
4
Dobre spekulacje, ale złe. Zakres tych przestrzeni nazw obejmuje cały plik.
Konrad Rudolph
Nie do końca prawda, jeśli zdefiniujesz anonimową przestrzeń nazw w innej przestrzeni nazw, nadal będzie ona obejmować tylko cały plik i może być postrzegana jako znajdująca się w tej przestrzeni nazw. Spróbuj.
Greg Rogers
Mogę się mylić, ale myślę, że nie, nie jest to plik: jest dostępny tylko dla kodu po anonimowej przestrzeni nazw. Jest to subtelna sprawa i zwykle nie chciałbym zanieczyszczać źródła wieloma anonimowymi przestrzeniami nazw ... Mimo to może to mieć zastosowanie.
paercebal,
0

Różnica polega na nazwie zniekształconego identyfikatora ( _ZN12_GLOBAL__N_11bEvs _ZL1b, co tak naprawdę nie ma znaczenia, ale oba są połączone z symbolami lokalnymi w tabeli symboli (brak .globaldyrektywy asm).

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

Jeśli chodzi o zagnieżdżoną anonimową przestrzeń nazw:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

Wszystkie anonimowe przestrzenie nazw pierwszego poziomu w jednostce tłumaczeniowej są ze sobą łączone, Wszystkie zagnieżdżone anonimowe przestrzenie nazw pierwszego poziomu w jednostce tłumaczącej są ze sobą łączone

Możesz także mieć zagnieżdżoną (wbudowaną) przestrzeń nazw w anonimowej przestrzeni nazw

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3
        .align 4
        .type   b, @object
        .size   b, 4

Możesz także mieć anonimowe wbudowane przestrzenie nazw, ale o ile wiem, inlinena anonimowej przestrzeni nazw ma 0 wpływ

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b: _Zoznacza, że ​​jest to zniekształcony identyfikator. Loznacza, że ​​jest to symbol lokalny przez static. 1jest długością identyfikatora, ba następnie identyfikatoremb

_ZN12_GLOBAL__N_11aE _Zoznacza, że ​​jest to zniekształcony identyfikator. Noznacza, że ​​to przestrzeń nazw 12jest długością anonimowej nazwy przestrzeni nazw _GLOBAL__N_1, następnie anonimowa nazwa przestrzeni nazw _GLOBAL__N_1, a następnie 1długość identyfikatora a, ajest identyfikatorem aiE zamyka identyfikator znajdujący się w przestrzeni nazw.

_ZN12_GLOBAL__N_11A1aE jest taki sam jak powyżej, ale zawiera inny poziom przestrzeni nazw 1A

Lewis Kelsey
źródło