Unbenannte / anonyme Namespaces vs. statische Funktionen

508

Eine Funktion von C ++ ist die Möglichkeit, unbenannte (anonyme) Namespaces wie folgt zu erstellen:

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

Sie würden denken, dass eine solche Funktion nutzlos wäre - da Sie den Namen des Namespace nicht angeben können, ist es unmöglich, von außen auf etwas darin zuzugreifen. Auf diese unbenannten Namespaces kann jedoch in der Datei zugegriffen werden, in der sie erstellt wurden, als hätten Sie eine implizite using-Klausel.

Meine Frage ist, warum oder wann dies der Verwendung statischer Funktionen vorzuziehen ist. Oder sind es im Wesentlichen zwei Möglichkeiten, genau dasselbe zu tun?

Head Geek
quelle
13
In C ++ 11 war die Verwendung von staticin diesem Zusammenhang nicht veraltet . Obwohl ein unbenannter Namespace eine überlegene Alternative zu darstelltstatic , gibt es Fälle, in denen er bei staticder Rettung fehlschlägt .
Legends2k

Antworten:

332

Der C ++ Standard liest in Abschnitt 7.3.1.1 Unbenannte Namespaces, Absatz 2:

Die Verwendung des statischen Schlüsselworts ist veraltet, wenn Objekte in einem Namespace-Bereich deklariert werden. Der unbenannte Namespace bietet eine überlegene Alternative.

Statisch gilt nur für Namen von Objekten, Funktionen und anonymen Vereinigungen, nicht für Typdeklarationen.

Bearbeiten:

Die Entscheidung, diese Verwendung des statischen Schlüsselworts zu verwerfen (die Sichtbarkeit einer Variablendeklaration in einer Übersetzungseinheit zu beeinflussen), wurde rückgängig gemacht ( ref ). In diesem Fall sind die Verwendung eines statischen oder eines unbenannten Namespace im Wesentlichen zwei Möglichkeiten, genau dasselbe zu tun. Weitere Informationen finden Sie in dieser SO-Frage.

Unbenannte Namespaces bieten weiterhin den Vorteil, dass Sie lokale Typen für Übersetzungseinheiten definieren können. Bitte lesen Sie diese SO-Frage für weitere Details.

Dank geht an Mike Percy , der mich darauf aufmerksam gemacht hat.

Luke
quelle
39
Head Geek fragt nach statischen Schlüsselwörtern, die nur für Funktionen verwendet werden. Das statische Schlüsselwort, das auf eine im Namespace-Bereich deklarierte Entität angewendet wird, gibt die interne Verknüpfung an. Die im anonymen Namespace deklarierte Entität verfügt über eine externe Verknüpfung (C ++ / 3.5), es wird jedoch garantiert, dass sie in einem eindeutig benannten Bereich lebt. Diese Anonymität des unbenannten Namespace verbirgt effektiv seine Erklärung und macht ihn nur innerhalb einer Übersetzungseinheit zugänglich. Letzteres funktioniert effektiv auf die gleiche Weise wie das statische Schlüsselwort.
Mloskot
5
Was ist der Nachteil der externen Verknüpfung? Könnte dies das Inlining beeinflussen?
Alex
17
Diejenigen im C ++ - Designkomitee, die sagten, dass das statische Schlüsselwort veraltet ist, haben wahrscheinlich nie mit einem riesigen C-Code in einem großen realen System gearbeitet ... (Sie sehen sofort ein statisches Schlüsselwort, aber nicht den anonymen Namespace, wenn es viele Deklarationen mit großen Kommentaren enthält Blöcke.)
Calmarius
23
Da diese Antwort bei Google als Top-Ergebnis für den "anonymen C ++ - Namespace" angezeigt wird, sollte beachtet werden, dass die Verwendung von statischen Daten nicht mehr veraltet ist. Weitere Informationen finden Sie unter stackoverflow.com/questions/4726570/… und open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 .
Michael Percy
2
@ErikAronesty Das klingt falsch. Haben Sie ein reproduzierbares Beispiel? Ab C ++ 11 - und sogar vorher in einigen Compilern - haben unbenannte namespaces implizit eine interne Verknüpfung, daher sollte es keinen Unterschied geben. Alle Probleme, die zuvor möglicherweise aufgrund einer schlechten Formulierung aufgetreten sind, wurden behoben, indem dies in C ++ 11 als Anforderung festgelegt wurde.
underscore_d
73

Durch das Einfügen von Methoden in einen anonymen Namespace wird verhindert, dass Sie versehentlich gegen die One Definition-Regel verstoßen. So können Sie sich niemals Gedanken darüber machen, wie Sie Ihre Hilfsmethoden wie andere Methoden benennen, mit denen Sie möglicherweise verknüpfen.

Und wie von Luke hervorgehoben, werden anonyme Namespaces vom Standard gegenüber statischen Elementen bevorzugt.

Hazzen
quelle
2
Ich bezog mich auf statische eigenständige Funktionen (dh Funktionen mit Dateibereich), nicht auf statische Elementfunktionen. Statische eigenständige Funktionen entsprechen weitgehend Funktionen in einem unbenannten Namespace, daher die Frage.
Head Geek
2
Ah; Nun, die ODR gilt immer noch. Bearbeitet, um Absatz zu entfernen.
Hazzen
Wie ich verstehe, funktioniert ODR für eine statische Funktion nicht, wenn sie im Header definiert ist und dieser Header in mehr als einer Übersetzungseinheit enthalten ist, oder? In diesem Fall erhalten Sie mehrere Kopien derselben Funktion
Andriy Tylychko
@Andy T: Sie sehen die "Mehrfachdefinitionen" bei eingeschlossenem Header nicht wirklich. Der Präprozessor kümmert sich darum. Es sei denn, es ist notwendig, die vom Präprozessor erzeugte Ausgabe zu untersuchen, was für mich eher exotisch und selten aussieht. Es gibt auch eine gute Praxis, "Wachen" in Header-Dateien aufzunehmen, wie zum Beispiel: "#ifndef SOME_GUARD - #define SOME_GUARD ...", um zu verhindern, dass der Präprozessor denselben Header zweimal einschließt.
Nikita Vorontsov
@NikitaVorontsov Der Wächter kann verhindern, dass derselbe Header in dieselbe Übersetzungseinheit aufgenommen wird, erlaubt jedoch mehrere Definitionen in verschiedenen Übersetzungseinheiten. Dies kann später zu einem Linkerfehler "mehrere Definitionen" führen.
Alex
37

Es gibt einen Randfall, in dem Statik einen überraschenden Effekt hat (zumindest für mich). Der C ++ 03-Standard besagt in 14.6.4.2/1:

Wenn bei einem Funktionsaufruf, der von einem Vorlagenparameter abhängt, der Funktionsname eine nicht qualifizierte ID, aber keine Vorlagen-ID ist , werden die Kandidatenfunktionen unter Verwendung der üblichen Suchregeln (3.4.1, 3.4.2) gefunden, mit der Ausnahme, dass:

  • Für den Teil der Suche mit unqualifizierter Namenssuche (3.4.1) werden nur Funktionsdeklarationen mit externer Verknüpfung aus dem Kontext der Vorlagendefinition gefunden.
  • Für den Teil der Suche unter Verwendung zugehöriger Namespaces (3.4.2) werden nur Funktionsdeklarationen mit externer Verknüpfung gefunden, die entweder im Kontext der Vorlagendefinition oder im Kontext der Vorlageninstanziierung gefunden wurden.

...

Der folgende Code wird aufgerufen foo(void*)und nicht foo(S const &)wie erwartet.

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

An sich ist dies wahrscheinlich keine so große Sache, aber es zeigt, dass für einen vollständig kompatiblen C ++ - Compiler (dh einen mit Unterstützung für export) das staticSchlüsselwort weiterhin Funktionen bietet, die auf keine andere Weise verfügbar sind.

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

Die einzige Möglichkeit, um sicherzustellen, dass die Funktion in unserem unbenannten Namespace nicht in Vorlagen mit ADL gefunden wird, besteht darin, sie zu erstellen static.

Update für Modern C ++

Ab C ++ '11 haben Mitglieder eines unbenannten Namespace implizit eine interne Verknüpfung (3.5 / 4):

Ein unbenannter Namespace oder ein direkt oder indirekt in einem unbenannten Namespace deklarierter Namespace ist intern verknüpft.

Gleichzeitig wurde 14.6.4.2/1 aktualisiert, um die Erwähnung der Verknüpfung zu entfernen (dies stammt aus C ++ '14):

Bei einem Funktionsaufruf, bei dem der Postfix-Ausdruck ein abhängiger Name ist, werden die Kandidatenfunktionen unter Verwendung der üblichen Suchregeln (3.4.1, 3.4.2) gefunden, mit der Ausnahme, dass:

  • Für den Teil der Suche mit unqualifizierter Namenssuche (3.4.1) werden nur Funktionsdeklarationen aus dem Kontext der Vorlagendefinition gefunden.

  • Für den Teil der Suche unter Verwendung zugehöriger Namespaces (3.4.2) werden nur Funktionsdeklarationen gefunden, die entweder im Kontext der Vorlagendefinition oder im Kontext der Vorlageninstanziierung gefunden wurden.

Das Ergebnis ist, dass dieser besondere Unterschied zwischen statischen und unbenannten Namespace-Mitgliedern nicht mehr besteht.

Richard Corden
quelle
3
Soll das Export-Schlüsselwort nicht kalt tot sein? Die einzigen Compiler, die "Export" unterstützen, sind experimentelle, und wenn es keine Überraschungen gibt, wird "Export" aufgrund unerwarteter Nebenwirkungen nicht einmal in anderen implementiert (zusätzlich dazu, dass dies nicht erwartet wurde)
paercebal
2
Siehe Herb Sutters Artikel im Subjet: gotw.ca/publications/mill23-x.htm
paercebal
3
Das Frontend der Edison Design Group (EDG) ist alles andere als experimentell. Es ist mit ziemlicher Sicherheit die standardkonformste C ++ - Implementierung der Welt. Der Intel C ++ - Compiler verwendet EDG.
Richard Corden
1
Welche C ++ - Funktion hat keine unerwarteten Nebenwirkungen? Beim Export wird eine unbenannte Namespace-Funktion von einer anderen TU gefunden - das ist so, als hätten Sie die Vorlagendefinition direkt eingefügt. Es wäre überraschender, wenn es nicht so wäre!
Richard Corden
Ich denke, Sie haben dort einen Tippfehler - um NS::Szu arbeiten, Smüssen Sie nicht drinnen sein namespace {}?
Eric
12

Ich habe kürzlich begonnen, statische Schlüsselwörter durch anonyme Namespaces in meinem Code zu ersetzen, stieß jedoch sofort auf ein Problem, bei dem die Variablen im Namespace in meinem Debugger nicht mehr zur Überprüfung verfügbar waren. Ich habe VC60 verwendet, daher weiß ich nicht, ob dies bei anderen Debuggern kein Problem darstellt. Meine Problemumgehung bestand darin, einen 'Modul'-Namespace zu definieren, in dem ich ihm den Namen meiner CPP-Datei gab.

In meiner Datei XmlUtil.cpp definiere ich beispielsweise einen Namespace XmlUtil_I { ... }für alle meine Modulvariablen und -funktionen. Auf diese Weise kann ich die XmlUtil_I::Qualifikation im Debugger anwenden, um auf die Variablen zuzugreifen. In diesem Fall _Iunterscheidet sich das von einem öffentlichen Namespace, wie XmlUtilich ihn möglicherweise an anderer Stelle verwenden möchte.

Ich nehme an, ein möglicher Nachteil dieses Ansatzes im Vergleich zu einem wirklich anonymen Ansatz besteht darin, dass jemand den gewünschten statischen Bereich verletzen könnte, indem er das Namespace-Qualifikationsmerkmal in anderen Modulen verwendet. Ich weiß jedoch nicht, ob das ein großes Problem ist.

Evg
quelle
7
Ich habe dies auch getan, aber mit #if DEBUG namespace BlahBlah_private { #else namespace { #endif, so dass der "Modul-Namespace" nur in Debug-Builds vorhanden ist und ansonsten ein echter anonymer Namespace verwendet wird. Es wäre schön, wenn Debugger eine gute Möglichkeit hätten, damit umzugehen. Doxygen wird auch dadurch verwirrt.
Kristopher Johnson
4
Ein unbenannter Namespace ist kein wirklich praktikabler Ersatz für statische. statisch bedeutet "wirklich wird dies nie außerhalb der TU verknüpft". unbenannter Namespace bedeutet "es wird immer noch als zufälliger Name exportiert, falls es von einer übergeordneten Klasse außerhalb der TU aufgerufen wird" ...
Erik Aronesty
7

Die Verwendung des statischen Schlüsselworts für diesen Zweck wird vom C ++ 98-Standard nicht mehr empfohlen. Das Problem mit static ist, dass es nicht für die Typdefinition gilt. Es ist auch ein überladenes Schlüsselwort, das in verschiedenen Kontexten auf unterschiedliche Weise verwendet wird, sodass unbenannte Namespaces die Dinge ein wenig vereinfachen.

Firas Assaad
quelle
1
Wenn Sie einen Typ nur in einer einzelnen Übersetzungseinheit verwenden möchten, deklarieren Sie ihn in der CPP-Datei. Es wird sowieso nicht von anderen Übersetzungseinheiten aus zugänglich sein.
Calmarius
4
Sie würden denken, nicht wahr? Wenn jedoch eine andere Übersetzungseinheit (= cpp-Datei) in derselben Anwendung jemals einen Typ mit demselben Namen deklariert, treten Probleme auf, die nur schwer zu beheben sind :-). Beispielsweise kann es vorkommen, dass beim Aufrufen von Methoden für den anderen die vtable für einen der Typen verwendet wird.
avl_sweden
1
Nicht mehr veraltet. Und Typ-Defs werden nicht exportiert, das ist also bedeutungslos. Statik ist nützlich für eigenständige Funktionen und globale Variablen. unbenannte Namespaces sind nützlich für Klassen.
Erik Aronesty
6

Aus Erfahrung werde ich nur bemerken, dass ältere Compiler manchmal Probleme damit haben können, obwohl dies die C ++ - Methode ist, ehemals statische Funktionen in den anonymen Namespace zu integrieren. Ich arbeite derzeit mit einigen Compilern für unsere Zielplattformen, und der modernere Linux-Compiler kann Funktionen problemlos in den anonymen Namespace einfügen.

Ein älterer Compiler, der unter Solaris ausgeführt wird und mit dem wir bis zu einer nicht spezifizierten zukünftigen Version verheiratet sind, akzeptiert ihn jedoch manchmal und kennzeichnet ihn manchmal als Fehler. Der Fehler ist nicht das, was mich beunruhigt, sondern das, was er tun könnte , wenn er ihn akzeptiert . Bis wir auf der ganzen Linie modern werden, verwenden wir immer noch statische (normalerweise klassenbezogene) Funktionen, bei denen wir den anonymen Namespace bevorzugen würden.

Don Wakefield
quelle
3

Wenn Sie ein statisches Schlüsselwort für eine Variable wie das folgende verwenden:

namespace {
   static int flag;
}

Es würde nicht in der Zuordnungsdatei angezeigt werden

Chris
quelle
7
Dann brauchen Sie überhaupt keinen anonymen Namespace.
Calmarius
2

Ein compilerspezifischer Unterschied zwischen anonymen Namespaces und statischen Funktionen ist beim Kompilieren des folgenden Codes zu erkennen.

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

Das Kompilieren dieses Codes mit VS 2017 (Angabe des Warnflags der Stufe 4 / W4 zum Aktivieren der Warnung C4505: Die nicht referenzierte lokale Funktion wurde entfernt ) und gcc 4.9 mit dem Flag -Wunused-function oder -Wall zeigen, dass VS 2017 nur eine Warnung für erzeugt die unbenutzte statische Funktion. gcc 4.9 und höher sowie clang 3.3 und höher erzeugen Warnungen für die nicht referenzierte Funktion im Namespace und eine Warnung für die nicht verwendete statische Funktion.

Live-Demo von gcc 4.9 und MSVC 2017

masrtis
quelle
2

Persönlich bevorzuge ich statische Funktionen aus folgenden Gründen gegenüber namenlosen Namespaces:

  • Allein aus der Funktionsdefinition geht hervor, dass es für die Übersetzungseinheit, in der es kompiliert wird, privat ist. Bei namenlosen Namespace müssen Sie möglicherweise scrollen und suchen, um festzustellen, ob sich eine Funktion in einem Namespace befindet.

  • Funktionen in Namespaces werden von einigen (älteren) Compilern möglicherweise als extern behandelt. In VS2017 sind sie noch extern. Aus diesem Grund möchten Sie eine Funktion möglicherweise auch dann als statisch markieren, wenn sie sich im namenlosen Namespace befindet.

  • Statische Funktionen verhalten sich in C oder C ++ sehr ähnlich, während namenlose Namespaces offensichtlich nur C ++ sind. namenlose Namespaces fügen auch zusätzliche Einrückungsstufen hinzu und das gefällt mir nicht :)

Ich bin also froh zu sehen, dass die Verwendung von Static für Funktionen nicht mehr veraltet ist .

Pavel P.
quelle
Funktionen in anonymen Namespaces sollen eine externe Verknüpfung haben. Sie sind nur verstümmelt, um sie einzigartig zu machen. Nur das staticSchlüsselwort wendet tatsächlich eine lokale Verknüpfung auf eine Funktion an. Auch würde sicherlich nur ein begeisterter Wahnsinniger tatsächlich Einrückungen für Namespaces hinzufügen?
Roflcopter4
0

Nachdem ich gerade erst beim Lesen Ihrer Frage von dieser Funktion erfahren habe, kann ich nur spekulieren. Dies scheint mehrere Vorteile gegenüber einer statischen Variablen auf Dateiebene zu bieten:

  • Anonyme Namespaces können ineinander verschachtelt werden und bieten mehrere Schutzstufen, denen Symbole nicht entkommen können.
  • In derselben Quelldatei können mehrere anonyme Namespaces platziert werden, wodurch unterschiedliche Bereiche auf statischer Ebene in derselben Datei erstellt werden.

Ich würde gerne erfahren, ob jemand anonyme Namespaces in echtem Code verwendet hat.

Commodore Jaeger
quelle
4
Gute Spekulationen, aber falsch. Der Umfang dieser Namespaces ist dateiweit.
Konrad Rudolph
Nicht genau richtig, wenn Sie einen anonymen Namespace in einem anderen Namespace definieren, ist dieser immer noch nur dateiweit und kann nur als innerhalb dieses Namespace befindlich angesehen werden. Versuch es.
Greg Rogers
Ich könnte mich irren, aber ich denke, nein, es ist nicht dateiweit: Es ist nur für den Code nach dem anonymen Namespace zugänglich . Dies ist eine subtile Sache, und normalerweise möchte ich eine Quelle nicht mit mehreren anonymen Namespaces verschmutzen ... Dennoch kann dies Verwendungszwecke haben.
Paercebal
0

Der Unterschied ist der Name des verstümmelten Bezeichners ( _ZN12_GLOBAL__N_11bEvs _ZL1b, was nicht wirklich wichtig ist, aber beide werden zu lokalen Symbolen in der Symboltabelle zusammengesetzt (Fehlen einer .globalasm-Direktive).

#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

Wie für einen verschachtelten anonymen Namespace:

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

Alle anonymen Namespaces der ersten Ebene in der Übersetzungseinheit werden miteinander kombiniert. Alle verschachtelten anonymen Namespaces der zweiten Ebene in der Übersetzungseinheit werden miteinander kombiniert

Sie können auch einen verschachtelten (Inline-) Namespace in einem anonymen Namespace haben

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

Sie können auch anonyme Inline-Namespaces verwenden, aber soweit ich das beurteilen kann, hat inlineein anonymer Namespace den Effekt 0

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

_ZL1b: _Zbedeutet, dass dies eine verstümmelte Kennung ist. Lbedeutet, es ist ein lokales Symbol durch static. 1ist die Länge des Bezeichners bund dann des Bezeichnersb

_ZN12_GLOBAL__N_11aE _Zbedeutet, dass dies eine verstümmelte Kennung ist. NDies bedeutet, dass dies ein Namespace 12ist. Dies ist die Länge des anonymen Namespace-Namens _GLOBAL__N_1, dann der anonyme Namespace-Name _GLOBAL__N_1, dann 1die Länge des Bezeichners a, ader Bezeichner aund Eschließt den Bezeichner, der sich in einem Namespace befindet.

_ZN12_GLOBAL__N_11A1aE ist das gleiche wie oben, außer dass es eine andere Namespace-Ebene gibt 1A

Lewis Kelsey
quelle