Spazi dei nomi senza nome / anonimi vs. funzioni statiche


508

Una caratteristica di C ++ è la possibilità di creare spazi dei nomi senza nome (anonimi), in questo modo:

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

Penseresti che una tale funzionalità sarebbe inutile - dal momento che non puoi specificare il nome dello spazio dei nomi, è impossibile accedere a qualcosa al suo interno dall'esterno. Ma questi spazi dei nomi senza nome sono accessibili all'interno del file in cui sono stati creati, come se avessi loro una clausola di utilizzo implicita.

La mia domanda è: perché o quando sarebbe preferibile utilizzare le funzioni statiche? O sono essenzialmente due modi per fare esattamente la stessa cosa?


13
In C ++ 11 l'utilizzo di staticin questo contesto era indefinito ; sebbene lo spazio dei nomi senza nome sia un'alternativa superiore astatic , ci sono casi in cui fallisce quando staticviene in soccorso .
legends2k,

Risposte:


332

Lo standard C ++ recita nella sezione 7.3.1.1 Spazi dei nomi senza nome, paragrafo 2:

L'uso della parola chiave statica è deprecato quando si dichiarano oggetti in un ambito dello spazio dei nomi, lo spazio dei nomi senza nome fornisce un'alternativa superiore.

Statico si applica solo ai nomi di oggetti, funzioni e sindacati anonimi, non al tipo di dichiarazioni.

Modificare:

La decisione di deprecare questo uso della parola chiave statica (influisce sulla visibilità di una dichiarazione variabile in un'unità di traduzione) è stata annullata ( rif ). In questo caso l'uso di uno spazio dei nomi statico o senza nome è tornato ad essere essenzialmente due modi di fare esattamente la stessa cosa. Per ulteriori discussioni, consultare questa domanda SO.

Gli spazi dei nomi senza nome hanno ancora il vantaggio di consentire di definire tipi di traduzione-unità-locali. Si prega di consultare questa domanda SO per maggiori dettagli.

Il merito va a Mike Percy per averlo portato alla mia attenzione.


39
Head Geek chiede informazioni sulle parole chiave statiche utilizzate solo per le funzioni. La parola chiave statica applicata all'entità dichiarata nell'ambito dello spazio dei nomi specifica il suo collegamento interno. L'entità dichiarata nello spazio dei nomi anonimo ha un collegamento esterno (C ++ / 3.5), tuttavia è garantito che viva in un ambito con un nome univoco. Questo anonimato dello spazio dei nomi senza nome nasconde effettivamente la sua dichiarazione rendendola accessibile solo all'interno di un'unità di traduzione. Quest'ultimo funziona efficacemente allo stesso modo della parola chiave statica.
mloskot,

5
qual è lo svantaggio del collegamento esterno? Questo potrebbe influenzare l'allineamento?
Alex,

17
Quelli del comitato di progettazione C ++ che hanno dichiarato che la parola chiave statica è obsoleta probabilmente non hanno mai lavorato con un enorme codice C in un grande sistema del mondo reale ... blocchi.)
Calmarius

23
Poiché questa risposta arriva su Google come risultato principale per "spazio dei nomi anonimo c ++", va notato che l'uso di static non è più deprecato. Vedere stackoverflow.com/questions/4726570/... e open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 per ulteriori informazioni.
Michael Percy,

2
@ErikAronesty Sembra sbagliato. Hai un esempio riproducibile? A partire da C ++ 11 - e anche prima in alcuni compilatori - i nomi anonimi namespacehanno implicitamente un collegamento interno, quindi non dovrebbero esserci differenze. Eventuali problemi che in precedenza potevano derivare da una formulazione scadente sono stati risolti rendendo questo requisito in C ++ 11.
underscore_d

73

L'inserimento di metodi in uno spazio dei nomi anonimo impedisce di violare accidentalmente la regola delle definizioni One , consentendoti di non preoccuparti mai di nominare i tuoi metodi di supporto allo stesso modo di altri metodi a cui puoi collegarti.

E, come sottolineato da Luke, gli spazi dei nomi anonimi sono preferiti dallo standard rispetto ai membri statici.


2
Mi riferivo a funzioni statiche autonome (ovvero funzioni con ambito di file), non a funzioni membro statiche. Le funzioni statiche autonome sono molto simili alle funzioni in uno spazio dei nomi senza nome, quindi la domanda.
Head Geek,

2
ah; bene, l'ODR si applica ancora. Modificato per rimuovere il paragrafo.
hazzen,

come ho capito, l'ODR per una funzione statica non funziona quando è definito nell'intestazione e questa intestazione è inclusa in più di un'unità di traduzione, giusto? in questo caso ricevi più copie della stessa funzione
Andriy Tylychko,

@Andy T: In realtà non vedi le "definizioni multiple" in caso di intestazione inclusa. Il preprocessore se ne occupa. A meno che non sia necessario studiare l'output generato dal preprocessore, che per me sembra piuttosto esotico e raro. Inoltre è buona norma includere "guard" nei file di intestazione, come: "#ifndef SOME_GUARD - #define SOME_GUARD ..." che dovrebbe impedire al preprocessore di includere due volte lo stesso header.
Nikita Vorontsov,

@NikitaVorontsov la guardia potrebbe impedire di includere la stessa intestazione nella stessa unità di traduzione, tuttavia consente più definizioni in unità di traduzione diverse. Ciò potrebbe causare errori del linker "più definizioni" lungo la linea.
Alex,

37

C'è un caso limite in cui l'elettricità statica ha un effetto sorprendente (almeno per me). Lo standard C ++ 03 afferma in 14.6.4.2/1:

Per una chiamata di funzione che dipende da un parametro modello, se il nome della funzione è un ID non qualificato ma non un ID modello , le funzioni candidate vengono rilevate utilizzando le normali regole di ricerca (3.4.1, 3.4.2) ad eccezione del fatto che:

  • Per la parte della ricerca che utilizza la ricerca di nomi non qualificati (3.4.1), vengono trovate solo dichiarazioni di funzioni con collegamento esterno dal contesto di definizione del modello.
  • Per la parte della ricerca che utilizza gli spazi dei nomi associati (3.4.2), vengono trovate solo dichiarazioni di funzioni con collegamento esterno trovate nel contesto di definizione del modello o nel contesto di istanza del modello.

...

Il seguente codice chiamerà foo(void*)e non foo(S const &)come ci si potrebbe aspettare.

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);
}

Di per sé questo non è probabilmente un grosso problema, ma evidenzia che per un compilatore C ++ pienamente conforme (cioè uno con supporto per export) la staticparola chiave avrà ancora funzionalità che non è disponibile in nessun altro modo.

// 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);
}

L'unico modo per garantire che la funzione nel nostro spazio dei nomi senza nome non venga trovata nei modelli che utilizzano ADL è farlo static.

Aggiornamento per C ++ moderno

A partire da C ++ '11, i membri di uno spazio dei nomi senza nome hanno implicitamente il collegamento interno (3.5 / 4):

Uno spazio dei nomi senza nome o uno spazio dei nomi dichiarato direttamente o indirettamente all'interno di uno spazio dei nomi senza nome ha un collegamento interno.

Ma allo stesso tempo, il 14.6.4.2/1 è stato aggiornato per rimuovere la menzione del collegamento (presa da C ++ '14):

Per una chiamata di funzione in cui l'espressione postfix è un nome dipendente, le funzioni candidate vengono trovate utilizzando le consuete regole di ricerca (3.4.1, 3.4.2) tranne che:

  • Per la parte della ricerca che utilizza la ricerca del nome non qualificata (3.4.1), vengono trovate solo le dichiarazioni di funzione dal contesto di definizione del modello.

  • Per la parte della ricerca che utilizza gli spazi dei nomi associati (3.4.2), vengono trovate solo le dichiarazioni di funzione trovate nel contesto di definizione del modello o nel contesto di istanza del modello.

Il risultato è che questa particolare differenza tra membri dello spazio dei nomi statici e senza nome non esiste più.


3
La parola chiave export non dovrebbe essere dead dead? Gli unici compilatori che supportano "export" sono sperimentali e, a parte le sorprese, "export" non sarà nemmeno implementato in altri a causa di effetti collaterali imprevisti (oltre a non farlo era previsto)
paercebal,

2
Vedi l'articolo di Herb Sutter sul sottoprogetto
paercebal,

3
Il front-end di Edison Design Group (EDG) è tutt'altro che sperimentale. È quasi certamente l'implementazione C ++ conforme più standard al mondo. Il compilatore Intel C ++ utilizza EDG.
Richard Corden,

1
Quale funzione C ++ non ha "effetti collaterali imprevisti"? Nel caso dell'esportazione, è possibile trovare una funzione dello spazio dei nomi senza nome da una TU diversa, ovvero come se fosse stata inclusa direttamente la definizione del modello. Sarebbe più sorprendente se non fosse così!
Richard Corden,

Penso che tu abbia un errore di battitura lì - per NS::Sfunzionare, non è Snecessario non essere dentro namespace {}?
Eric

12

Di recente ho iniziato a sostituire le parole chiave statiche con spazi dei nomi anonimi nel mio codice, ma ho subito riscontrato un problema in cui le variabili nello spazio dei nomi non erano più disponibili per l'ispezione nel mio debugger. Stavo usando VC60, quindi non so se si tratta di un problema con altri debugger. La mia soluzione era quella di definire uno spazio dei nomi 'modulo', dove gli ho dato il nome del mio file cpp.

Ad esempio, nel mio file XmlUtil.cpp, definisco uno spazio XmlUtil_I { ... }dei nomi per tutte le variabili e le funzioni del mio modulo. In questo modo posso applicare la XmlUtil_I::qualifica nel debugger per accedere alle variabili. In questo caso, lo _Idistingue da uno spazio dei nomi pubblico come quello XmlUtilche potrei voler usare altrove.

Suppongo che un potenziale svantaggio di questo approccio rispetto a uno veramente anonimo sia che qualcuno potrebbe violare l'ambito statico desiderato utilizzando il qualificatore dello spazio dei nomi in altri moduli. Non so se questa sia una delle maggiori preoccupazioni.


7
L'ho fatto anche io, ma con #if DEBUG namespace BlahBlah_private { #else namespace { #endif, quindi lo "spazio dei nomi dei moduli" è presente solo nelle build di debug e lo spazio dei nomi vero anonimo viene utilizzato altrimenti. Sarebbe bello se i debugger fornissero un modo piacevole per gestirlo. Anche Doxygen ne viene confuso.
Kristopher Johnson,

4
lo spazio dei nomi senza nome non è in realtà un sostituto praticabile per statico. statico significa "davvero questo non viene mai collegato all'esterno della TU". lo spazio dei nomi senza nome significa "viene ancora esportato, come un nome casuale, nel caso venga chiamato da una classe genitore che è al di fuori della TU" ...
Erik Aronesty,

7

L'uso della parola chiave statica a tale scopo è deprecato dallo standard C ++ 98. Il problema con static è che non si applica alla definizione del tipo. È anche una parola chiave sovraccarica utilizzata in diversi modi in contesti diversi, quindi spazi dei nomi senza nome semplificano un po 'le cose.


1
Se si desidera utilizzare un tipo solo in una singola unità di traduzione, dichiararlo nel file .cpp. Non sarà comunque accessibile da altre unità di traduzione.
Calmarius

4
Penseresti, no? Ma se un'altra unità di traduzione (= file cpp) nella stessa applicazione dichiara mai un tipo con lo stesso nome, ci sono problemi piuttosto difficili da debug :-). Ad esempio, è possibile che si verifichino situazioni in cui la vtable per uno dei tipi viene utilizzata quando si chiamano metodi sull'altro.
avl_sweden,

1
Non più deprecato. E i def di tipo non vengono esportati, quindi non ha senso. la statica è utile per le funzioni autonome e le variabili globali. spazi dei nomi senza nome sono utili per le classi.
Erik Aronesty,

6

Per esperienza, noterò solo che mentre è il modo C ++ di inserire funzioni precedentemente statiche nello spazio dei nomi anonimo, i compilatori più vecchi a volte possono avere problemi con questo. Attualmente lavoro con alcuni compilatori per le nostre piattaforme di destinazione e il più moderno compilatore Linux sta bene inserendo le funzioni nello spazio dei nomi anonimo.

Ma un compilatore più vecchio in esecuzione su Solaris, a cui siamo tenuti fino a una versione futura non specificata, a volte lo accetterà e altre volte lo segnalerà come un errore. L'errore non è ciò che mi preoccupa, è ciò che potrebbe fare quando lo accetta . Quindi fino a quando non diventiamo moderni su tutta la linea, stiamo ancora usando funzioni statiche (di solito nell'ambito della classe) in cui preferiremmo lo spazio dei nomi anonimo.


3

Inoltre, se si utilizza una parola chiave statica su una variabile come questo esempio:

namespace {
   static int flag;
}

Non sarebbe visibile nel file di mapping


7
Quindi non hai bisogno di uno spazio dei nomi anonimo.
Calmarius

2

Una differenza specifica del compilatore tra spazi dei nomi anonimi e funzioni statiche può essere vista compilando il seguente codice.

#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;
}

Compilare questo codice con VS 2017 (specificando il flag di avviso di livello 4 / W4 per abilitare l' avviso C4505: la funzione locale non referenziata è stata rimossa ) e gcc 4.9 con la funzione -Wunused-function o -Wall mostra che VS 2017 produrrà solo un avviso per la funzione statica inutilizzata. gcc 4.9 e versioni successive, così come clang 3.3 e versioni successive, produrranno avvisi per la funzione non referenziata nello spazio dei nomi e anche un avviso per la funzione statica inutilizzata.

Demo live di gcc 4.9 e MSVC 2017


2

Personalmente preferisco le funzioni statiche rispetto agli spazi dei nomi senza nome per i seguenti motivi:

  • Dalla definizione della funzione è ovvio e chiaro che è privato per l'unità di traduzione in cui è compilato. Con lo spazio dei nomi senza nome potrebbe essere necessario scorrere e cercare per vedere se una funzione si trova in uno spazio dei nomi.

  • Le funzioni negli spazi dei nomi potrebbero essere trattate come esterne da alcuni compilatori (più vecchi). In VS2017 sono ancora esterni. Per questo motivo, anche se una funzione si trova nello spazio dei nomi senza nome, è comunque possibile contrassegnarli come statici.

  • Le funzioni statiche si comportano in modo molto simile in C o C ++, mentre gli spazi dei nomi senza nome sono ovviamente solo C ++. gli spazi dei nomi senza nome aggiungono anche un ulteriore livello di rientro e non mi piace :)

Quindi, sono felice di vedere che l'uso di static per le funzioni non è più deprecato .


Le funzioni in spazi dei nomi anonimi dovrebbero avere un collegamento esterno. Sono solo distrutti per renderli unici. Solo la staticparola chiave applica effettivamente il collegamento locale a una funzione. Inoltre, sicuramente solo un pazzo delirante aggiungerebbe effettivamente il rientro per gli spazi dei nomi?
Roflcopter4,

0

Avendo appreso di questa funzione solo ora mentre leggo la tua domanda, posso solo speculare. Ciò sembra offrire diversi vantaggi rispetto a una variabile statica a livello di file:

  • Gli spazi dei nomi anonimi possono essere nidificati l'uno nell'altro, fornendo più livelli di protezione da cui i simboli non possono sfuggire.
  • Diversi spazi dei nomi anonimi possono essere inseriti nello stesso file di origine, creando in effetti diversi ambiti a livello statico all'interno dello stesso file.

Sarei interessato a sapere se qualcuno ha usato spazi dei nomi anonimi in codice reale.


4
Buone speculazioni, ma sbagliate. L'ambito di questi spazi dei nomi è a livello di file.
Konrad Rudolph,

Non esattamente vero, se si definisce uno spazio dei nomi anonimo all'interno di un altro spazio dei nomi, è ancora largo solo il file e può essere visto solo all'interno di quello spazio dei nomi. Provalo.
Greg Rogers,

Potrei sbagliarmi ma, immagino che no, non è a livello di file: è accessibile solo al codice dopo lo spazio dei nomi anonimo. Questa è una cosa sottile, e di solito non vorrei inquinare una fonte con più spazi dei nomi anonimi ... Tuttavia, questo può avere degli usi.
paercebal,

0

La differenza è il nome dell'identificatore modificato ( _ZN12_GLOBAL__N_11bEvs _ZL1b, che non ha molta importanza, ma entrambi sono assemblati in simboli locali nella tabella dei simboli (assenza della .globaldirettiva 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

Per quanto riguarda uno spazio dei nomi anonimo nidificato:

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

Tutti gli spazi dei nomi anonimi di 1 ° livello nell'unità di traduzione sono combinati tra loro, Tutti gli spazi dei nomi anonimi di 2 ° livello nell'unità di traduzione sono combinati tra loro

Puoi anche avere uno spazio dei nomi nidificato (in linea) in uno spazio dei nomi anonimo

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

Puoi anche avere spazi dei nomi in linea anonimi, ma per quanto ne so, inlinesu uno spazio dei nomi anonimo ha effetto 0

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

_ZL1b: _Zsignifica che si tratta di un identificatore alterato. Lsignifica che è un simbolo locale attraverso static. 1è la lunghezza dell'identificatore be quindi dell'identificatoreb

_ZN12_GLOBAL__N_11aE _Zsignifica che questo è un identificatore alterato. Nsignifica che questo è uno spazio dei nomi 12è la lunghezza del nome dello spazio dei nomi anonimo _GLOBAL__N_1, quindi il nome dello spazio dei nomi anonimo _GLOBAL__N_1, quindi 1è la lunghezza dell'identificatore a, aè l'identificatore ae Echiude l'identificatore che risiede in uno spazio dei nomi.

_ZN12_GLOBAL__N_11A1aE è uguale a quello precedente, tranne per il fatto che è presente un altro livello dello spazio dei nomi 1A

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.