Namespaces (C++)

Ein Namespace ist ein deklarativer Bereich, der einen Gültigkeitsbereich für die darin enthaltenen Bezeichner darstellt (die Namen von Typen, Funktionen, Variablen usw.). Namespaces werden verwendet, um Code in logischen Gruppen zu organisieren und Namenskonflikte zu vermeiden, die insbesondere dann auftreten können, wenn die Codebasis mehrere Bibliotheken enthält. Alle Bezeichner im Gültigkeitsbereich des Namespaces sind ohne Qualifizierung füreinander sichtbar. Bezeichner außerhalb des Namespaces können auf die Member zugreifen, indem sie den vollqualifizierten Namen für jeden Bezeichner verwenden, z std::vector<std::string> vec;. B. oder eine using-Deklaration für einen einzelnen Bezeichner (using std::string) oder eine using-Direktive für alle Bezeichner im Namespace (using namespace std;). Der Code in Headerdateien muss immer den vollqualifizierten Namespacenamen verwenden.

Das folgende Beispiel zeigt eine Namespacedeklaration und drei Verfahren, mit denen Code außerhalb des Namespaces auf die Member zugreifen kann.

namespace ContosoData
{
    class ObjectManager
    {
    public:
        void DoSomething() {}
    };
    void Func(ObjectManager) {}
}

Verwendung des vollqualifizierten Namens:

ContosoData::ObjectManager mgr;
mgr.DoSomething();
ContosoData::Func(mgr);

Verwendung einer using-Deklaration, um einen Bezeichner in den Gültigkeitsbereich einzubinden:

using ContosoData::ObjectManager;
ObjectManager mgr;
mgr.DoSomething();

Verwendung einer using-Anweisung, um den gesamten Namespaceinhalt in den Gültigkeitsbereich einzubinden:

using namespace ContosoData;

ObjectManager mgr;
mgr.DoSomething();
Func(mgr);

using-Direktiven

Mit using der Direktive können alle Namen in einer namespace ohne den Namespacenamen als expliziter Qualifizierer verwendet werden. Verwenden Sie eine Using-Direktive in einer Implementierungsdatei (d. h. *.cpp), wenn Sie mehrere verschiedene Bezeichner in einem Namespace verwenden; Wenn Sie nur einen oder zwei Bezeichner verwenden, sollten Sie eine Using-Deklaration in Betracht ziehen, um diese Bezeichner nur in den Bereich zu bringen und nicht alle Bezeichner im Namespace. Wenn eine lokale Variable denselben Namen wie eine Namespacevariable besitzt , wird die Namespacevariable ausgeblendet. Es ist ein Fehler, wenn eine Namespacevariable mit dem gleichen Namen wie eine globale Variable vorliegt.

Hinweis

Eine using-Direktive kann am Anfang einer CPP-Datei (im Dateigültigkeitsbereich) platziert werden oder innerhalb einer Klassen- oder Funktionsdefinition.

Vermeiden Sie generell das Einfügen von using-Direktiven in Headerdateien (*.h), da jede Datei, die diesen Header enthält, den gesamten Namespaceinhalt in den Gültigkeitsbereich einbinden kann; dies kann dazu führen, dass Probleme mit ausgeblendeten Namen und Namenskonflikte auftreten, die sehr schwer zu beheben sind. Verwenden Sie immer vollqualifizierte Namen in einer Headerdatei. Wenn diese Namen zu lang werden, können Sie einen Namespacealias verwenden, um sie zu kürzen. (Siehe unten.)

Deklarieren von Namespaces und Namespacemembern

In der Regel deklarieren Sie einen Namespace in einer Headerdatei. Wenn sich die Funktionsimplementierungen in einer separaten Datei befinden, qualifizieren Sie die Funktionsnamen, wie in diesem Beispiel gezeigt.

// contosoData.h
#pragma once
namespace ContosoDataServer
{
    void Foo();
    int Bar();
}

Funktionsimplementierungen in contosodata.cpp sollten den vollqualifizierten Namen verwenden, auch wenn Sie eine using Direktive am Anfang der Datei platzieren:

#include "contosodata.h"
using namespace ContosoDataServer;

void ContosoDataServer::Foo() // use fully-qualified name here
{
   // no qualification needed for Bar()
   Bar();
}

int ContosoDataServer::Bar(){return 0;}

Ein Namespace kann in mehreren Blöcken in einer einzelnen Datei oder in mehreren Dateien deklariert werden. Der Compiler verbindet die Teile während der Vorverarbeitung, und der resultierende Namespace enthält alle Member, die in allen Teilen deklariert sind. Ein Beispiel hierfür ist der std-Namespace, der in jeder der Headerdateien in der Standardbibliothek deklariert wird.

Member eines benannten Namespaces können außerhalb des Namespaces definiert werden, in dem sie durch explizite Qualifizierung des definierten Namens deklariert werden. Allerdings muss sich die Definition nach der Deklaration in einem Namespace befinden, der den Namespace der Deklaration einschließt. Beispiel:

// defining_namespace_members.cpp
// C2039 expected
namespace V {
    void f();
}

void V::f() { }        // ok
void V::g() { }        // C2039, g() is not yet a member of V

namespace V {
    void g();
}

Dieser Fehler kann auftreten, wenn Namespacemember über mehrere Headerdateien deklariert werden und Sie diese Header nicht in der richtigen Reihenfolge eingeschlossen haben.

Der globale Namespace

Wenn ein Bezeichner nicht in einem expliziten Namespace deklariert ist, ist er Teil des impliziten globalen Namespaces. Versuchen Sie im Allgemeinen, Deklarationen im globalen Bereich nach Möglichkeit zu vermeiden, mit Ausnahme des Einstiegspunkts Standard Function, der sich im globalen Namespace befinden muss. Um einen globalen Bezeichner explizit zu qualifizieren, verwenden Sie den Bereichsauflösungsoperator ohne Namen, wie in ::SomeFunction(x);. Dadurch wird der Bezeichner von allen anderen Elementen gleichen Namens in allen anderen Namespaces unterschieden, und es vereinfacht außerdem das Verständnis Ihres Codes für andere.

Der Namespace "std"

Alle C++-Standardbibliothekstypen und -funktionen werden in den Namespaces oder Namespaces deklariert, die std innerhalb stdgeschachtelt sind.

Geschachtelte Namespaces

Namespaces können geschachtelt werden. Ein gewöhnlicher geschachtelter Namespace hat keinen qualifizierten Zugriff auf die Member seines übergeordneten Elements, aber die übergeordneten Member haben keinen nicht qualifizierten Zugriff auf den geschachtelten Namespace (es sei denn, er wird als Inline deklariert), wie im folgenden Beispiel gezeigt:

namespace ContosoDataServer
{
    void Foo();

    namespace Details
    {
        int CountImpl;
        void Ban() { return Foo(); }
    }

    int Bar(){...};
    int Baz(int i) { return Details::CountImpl; }
}

Gewöhnliche geschachtelte Namespaces können verwendet werden, um interne Implementierungsdetails zu kapseln, die nicht Teil der öffentlichen Schnittstelle des übergeordnete Namespaces sind.

Inlinenamespaces (C++11)

Im Gegensatz zu einem gewöhnlichen geschachtelten Namespace werden Member eines Inlinenamespaces als Member des übergeordneten Namespaces behandelt. Dieses Merkmal ermöglicht es, die argumentabhängige Suche für überladene Funktionen auch für Funktionen zu verwenden, die Überladungen in einem übergeordneten und einem geschachtelten Inlinenamespace aufweisen. Außerdem können Sie dadurch eine Spezialisierung in einem übergeordneten Namespace für eine Vorlage deklarieren, die im Inlinenamespace deklariert ist. Das folgende Beispiel zeigt, wie externer Code standardmäßig an den Inlinenamespace gebunden wird:

// Header.h
#include <string>

namespace Test
{
    namespace old_ns
    {
        std::string Func() { return std::string("Hello from old"); }
    }

    inline namespace new_ns
    {
        std::string Func() { return std::string("Hello from new"); }
    }
}

// main.cpp
#include "header.h"
#include <string>
#include <iostream>

int main()
{
    using namespace Test;
    using namespace std;

    string s = Func();
    std::cout << s << std::endl; // "Hello from new"
    return 0;
}

Das folgende Beispiel zeigt, wie Sie eine Spezialisierung in einem übergeordneten Namespace einer Vorlage deklarieren, die in einem Inlinenamespace deklariert ist:

namespace Parent
{
    inline namespace new_ns
    {
         template <typename T>
         struct C
         {
             T member;
         };
    }
     template<>
     class C<int> {};
}

Inlinenamespaces können als Mechanismus für die Versionskontrolle verwendet werden, um Änderungen an der öffentlichen Schnittstelle einer Bibliothek zu verwalten. Sie können beispielsweise einen einzelnen übergeordneten Namespace erstellen und jede Version der Schnittstelle in einem eigenen Namespace kapseln, der innerhalb des übergeordneten Namespaces geschachtelt ist. Der Namespace, der die aktuelle oder bevorzugte Version enthält, wird als Inlinenamespace qualifiziert und daher so verfügbar gemacht, als handele es sich um einen direkten Member des übergeordnete Namespaces. Clientcode, der Parent::Class aufruft, wird automatisch an den neuen Code gebunden. Clients, die die ältere Version verwenden möchten, können weiterhin darauf zugreifen, indem sie den vollqualifizierten Pfad zu dem geschachtelten Namespace verwenden, der diesen Code enthält.

Das inline-Schlüsselwort muss auf die erste Deklaration des Namespaces in einer Kompilierungseinheit angewendet werden.

Das folgende Beispiel zeigt zwei Versionen einer Schnittstelle, beide in einem geschachtelten Namespace. Der v_20-Namespace weist einige Änderungen gegenüber der v_10-Schnittstelle auf und ist als Inline gekennzeichnet. Clientcode, der die neue Bibliothek verwendet Contoso::Funcs::Add aufruft, ruft die v_20-Version auf. Bei Code, der versucht, Contoso::Funcs::Divide aufzurufen, wird jetzt ein Fehler zur Kompilierzeit ausgegeben. Wenn diese Funktion wirklich benötigt wird, kann durch explizites Aufrufen von Contoso::v_10::Funcs::Divide immer noch auf die v_10-Version zugegriffen werden.

namespace Contoso
{
    namespace v_10
    {
        template <typename T>
        class Funcs
        {
        public:
            Funcs(void);
            T Add(T a, T b);
            T Subtract(T a, T b);
            T Multiply(T a, T b);
            T Divide(T a, T b);
        };
    }

    inline namespace v_20
    {
        template <typename T>
        class Funcs
        {
        public:
            Funcs(void);
            T Add(T a, T b);
            T Subtract(T a, T b);
            T Multiply(T a, T b);
            std::vector<double> Log(double);
            T Accumulate(std::vector<T> nums);
        };
    }
}

Namespacealiase

Namespacenamen müssen eindeutig sein, was bedeutet, dass sie häufig nicht zu kurz sein dürfen. Wenn die Länge eines Namens das Lesen von Code erschwert oder mühsam in eine Headerdatei eingegeben werden kann, in der die Verwendung von Direktiven nicht verwendet werden kann, können Sie einen Namespacealias erstellen, der als Abkürzung für den tatsächlichen Namen dient. Beispiel:

namespace a_very_long_namespace_name { class Foo {}; }
namespace AVLNN = a_very_long_namespace_name;
void Bar(AVLNN::Foo foo){ }

anonyme oder unbenannte Namespaces

Sie können einen expliziten Namespace erstellen, ihm aber keinen Namen zuweisen:

namespace
{
    int MyFunc(){}
}

Dies wird als nicht benannter oder anonymer Namespace bezeichnet und ist nützlich, wenn Sie Variablendeklarationen für Code in anderen Dateien unsichtbar machen möchten (d. h. interne Verknüpfungen zuweisen), ohne einen benannten Namespace erstellen zu müssen. Der gesamte Code in einer bestimmten Datei kann die Bezeichner in einem unbenannten Namespace sehen, aber die Bezeichner, zusammen mit dem Namespace selbst, sind außerhalb dieser Datei nicht sichtbar – oder genauer gesagt außerhalb der Übersetzungseinheit.

Siehe auch

Deklarationen und Definitionen