Namensräume
Namensräume bieten eine Methode, um Namenskonflikte in großen Projekten zu vermeiden.
Innerhalb eines Namensraumblocks deklarierte Entitäten werden einem Namensraum-Scope zugeordnet, was verhindert, dass sie mit gleichnamigen Entitäten in anderen Scopes verwechselt werden.
Außerhalb aller Namensraumblöcke deklarierte Entitäten gehören zum globalen Namensraum. Der globale Namensraum gehört zum globalen Scope und kann explizit mit einem führenden :: angesprochen werden. Obwohl er keine Deklaration hat, ist der globale Namensraum kein namensloser Namensraum.
Mehrere Namensraumblöcke mit demselben Namen sind erlaubt. Alle Deklarationen innerhalb dieser Blöcke werden im selben Namensraum-Scope deklariert.
Inhalt |
[bearbeiten] Syntax
namespace ns-name { declarations } |
(1) | ||||||||
inline namespace ns-name { declarations } |
(2) | (seit C++11) | |||||||
namespace { declarations } |
(3) | ||||||||
ns-name :: member-name |
(4) | ||||||||
using namespace ns-name ; |
(5) | ||||||||
using ns-name :: member-name ; |
(6) | ||||||||
namespace name = qualified-namespace ; |
(7) | ||||||||
namespace ns-name :: member-name { declarations } |
(8) | (seit C++17) | |||||||
namespace ns-name :: inline member-name { declarations } |
(9) | (seit C++20) | |||||||
[bearbeiten] Erklärung
[bearbeiten] Namensräume
inline(optional) namespace attr (optional) identifier { namespace-body } |
|||||||||
inline
|
- | (seit C++11) falls vorhanden, macht dies einen Inline-Namensraum (siehe unten). Kann nicht bei der extension-namespace-definition vorkommen, wenn die original-namespace-definition nicht inline verwendet hat. | ||
| attr | - | (seit C++17) optionale Sequenz von beliebig vielen Attributen | ||
| identifier | - | entweder
| ||
| namespace-body | - | möglicherweise leere Sequenz von Deklarationen jeglicher Art (einschließlich Klassen- und Funktionsdefinitionen sowie verschachtelter Namensräume) |
Namensraumdefinitionen sind nur im Namensraum-Scope, einschließlich des globalen Scopes, erlaubt.
Um einen bestehenden Namensraum erneut zu öffnen (formal, um eine extension-namespace-definition zu sein), muss die Suche nach dem identifier, der in der Namensraumdefinition verwendet wird, zu einem Namensraum-Namen (nicht zu einem Namensraum-Alias) aufgelöst werden, der als Mitglied des umgebenden Namensraums oder eines Inline-Namensraums innerhalb eines umgebenden Namensraums deklariert wurde.
Der namespace-body definiert einen Namensraum-Scope, der die Namenssuche beeinflusst.
Alle Namen, die durch die Deklarationen innerhalb von namespace-body (einschließlich verschachtelter Namensraumdefinitionen) eingeführt werden, werden zu Mitgliedern des Namensraums identifier, unabhängig davon, ob diese Namensraumdefinition die ursprüngliche Namensraumdefinition ist (die identifier eingeführt hat) oder eine erweiterte Namensraumdefinition (die den bereits definierten Namensraum "wiedereröffnet").
Ein Namensraummitglied, das innerhalb eines Namensraumkörpers deklariert wurde, kann außerhalb davon durch explizite Qualifizierung definiert oder neu deklariert werden.
namespace Q { namespace V // V is a member of Q, and is fully defined within Q { // namespace Q::V { // C++17 alternative to the lines above class C { void m(); }; // C is a member of V and is fully defined within V // C::m is only declared void f(); // f is a member of V, but is only declared here } void V::f() // definition of V's member f outside of V // f's enclosing namespaces are still the global namespace, Q, and Q::V { extern void h(); // This declares ::Q::V::h } void V::C::m() // definition of V::C::m outside of the namespace (and the class body) // enclosing namespaces are the global namespace, Q, and Q::V {} }
Definitionen und Neudeklarationen außerhalb des Namensraums sind nur erlaubt
- nach dem Deklarationspunkt,
- im Namensraum-Scope und
- in Namensräumen, die den ursprünglichen Namensraum einschließen (einschließlich des globalen Namensraums).
Außerdem müssen sie die Syntax eines qualifizierten Identifikators verwenden.
namespace Q { namespace V // original-namespace-definition for V { void f(); // declaration of Q::V::f } void V::f() {} // OK void V::g() {} // Error: g() is not yet a member of V namespace V // extension-namespace-definition for V { void g(); // declaration of Q::V::g } } namespace R // not an enclosing namespace for Q { void Q::V::g() {} // Error: cannot define Q::V::g inside R } void Q::V::g() {} // OK: global namespace encloses Q
Namen, die durch friend-Deklarationen innerhalb einer nicht-lokalen Klasse X eingeführt werden, werden zu Mitgliedern des innersten umgebenden Namensraums von X, sind aber für die normale Namenssuche (weder unqualifiziert noch qualifiziert) nicht sichtbar, es sei denn, eine übereinstimmende Deklaration wird im Namensraum-Scope bereitgestellt, entweder vor oder nach der Klassendefinition. Ein solcher Name kann durch ADL gefunden werden, die sowohl Namensräume als auch Klassen berücksichtigt.
Nur der innerste umgebende Namensraum wird bei solchen friend-Deklarationen berücksichtigt, um zu entscheiden, ob der Name mit einem zuvor deklarierten Namen kollidiert.
void h(int); namespace A { class X { friend void f(X); // A::f is a friend class Y { friend void g(); // A::g is a friend friend void h(int); // A::h is a friend, no conflict with ::h }; }; // A::f, A::g and A::h are not visible at namespace scope // even though they are members of the namespace A X x; void g() // definition of A::g { f(x); // A::X::f is found through ADL } void f(X) {} // definition of A::f void h(int) {} // definition of A::h // A::f, A::g and A::h are now visible at namespace scope // and they are also friends of A::X and A::X::Y }
Inline-NamensräumeEin Inline-Namensraum ist ein Namensraum, der das optionale Schlüsselwort Mitglieder eines Inline-Namensraums werden in vielen Situationen (unten aufgelistet) so behandelt, als wären sie Mitglieder des umgebenden Namensraums. Diese Eigenschaft ist transitiv: Wenn ein Namensraum N einen Inline-Namensraum M enthält, der wiederum einen Inline-Namensraum O enthält, dann können die Mitglieder von O so verwendet werden, als wären sie Mitglieder von M oder N.
// in C++14, std::literals and its member namespaces are inline { using namespace std::string_literals; // makes visible operator""s // from std::literals::string_literals auto str = "abc"s; } { using namespace std::literals; // makes visible both // std::literals::string_literals::operator""s // and std::literals::chrono_literals::operator""s auto str = "abc"s; auto min = 60s; } { using std::operator""s; // makes both std::literals::string_literals::operator""s // and std::literals::chrono_literals::operator""s visible auto str = "abc"s; auto min = 60s; } Hinweis: Die Regel über Spezialisierungen ermöglicht die Versionsverwaltung von Bibliotheken: unterschiedliche Implementierungen einer Bibliotheks-Vorlage können in verschiedenen Inline-Namensräumen definiert werden, während es dem Benutzer immer noch erlaubt ist, den übergeordneten Namensraum mit einer expliziten Spezialisierung der Primär-Vorlage zu erweitern. Führen Sie diesen Code aus namespace Lib { inline namespace Lib_1 { template<typename T> class A; } template<typename T> void g(T) { /* ... */ } } /* ... */ struct MyClass { /* ... */ }; namespace Lib { template<> class A<MyClass> { /* ... */ }; } int main() { Lib::A<MyClass> a; g(a); // ok, Lib is an associated namespace of A } |
(seit C++11) |
[bearbeiten] Namenslose Namensräume
Die unnamed-namespace-definition ist eine Namensraumdefinition der Form
inline(optional) namespace attr (optional) { namespace-body } |
|||||||||
inline
|
- | (seit C++11) falls vorhanden, macht dies einen Inline-Namensraum. |
| attr | - | (seit C++17) optionale Sequenz von beliebig vielen Attributen |
Diese Definition wird als Definition eines Namensraums mit einem eindeutigen Namen behandelt, und eine using-directive im aktuellen Scope, die diesen namenslosen Namensraum nominiert (Hinweis: die implizit hinzugefügte using-directive macht den Namensraum für die qualifizierte Namenssuche und nicht-qualifizierte Namenssuche verfügbar, aber nicht für die argument-abhängige Suche). Der eindeutige Name ist im gesamten Programm eindeutig, aber innerhalb einer Übersetzungseinheit entspricht jede namenslose Namensraumdefinition demselben eindeutigen Namen: mehrere namenslose Namensraumdefinitionen im selben Scope bezeichnen denselben namenslosen Namensraum.
namespace { int i; // defines ::(unique)::i } void f() { i++; // increments ::(unique)::i } namespace A { namespace { int i; // A::(unique)::i int j; // A::(unique)::j } void g() { i++; } // A::(unique)::i++ } using namespace A; // introduces all names from A into global namespace void h() { i++; // error: ::(unique)::i and ::A::(unique)::i are both in scope A::i++; // ok, increments ::A::(unique)::i j++; // ok, increments ::A::(unique)::j }
|
Auch wenn Namen in einem namenslosen Namensraum mit externer Bindung deklariert werden können, sind sie von anderen Übersetzungseinheiten aus nie zugänglich, da ihr Namensraumname eindeutig ist. |
(bis C++11) |
|
Namenslose Namensräume sowie alle Namensräume, die direkt oder indirekt innerhalb eines namenslosen Namensraums deklariert werden, haben eine interne Bindung, was bedeutet, dass jeder Name, der innerhalb eines namenslosen Namensraums deklariert wird, eine interne Bindung hat. |
(seit C++11) |
[bearbeiten] Using-Deklarationen
Führt einen Namen, der an anderer Stelle definiert ist, in den Deklarationsbereich ein, in dem diese Using-Deklaration erscheint.
using typename(optional) nested-name-specifier unqualified-id ; |
(bis C++17) | ||||||||
using declarator-list ; |
(seit C++17) | ||||||||
typename
|
- | das Schlüsselwort typename kann nach Bedarf verwendet werden, um abhängige Namen aufzulösen, wenn die Using-Deklaration einen Member-Typ aus einer Basisklasse in einer Klassenvorlage einführt. |
| nested-name-specifier | - | eine Sequenz von Namen und Scope-Auflösungsoperatoren ::, die mit einem Scope-Auflösungsoperator endet. Ein einzelnes :: bezieht sich auf den globalen Namensraum. |
| unqualified-id | - | ein id-expression |
| declarator-list | - | durch Kommata getrennte Liste von einem oder mehreren Deklaratoren der Form typename(optional) nested-name-specifier unqualified-id. Ein Deklarator kann von einer Ellipse gefolgt werden, um eine Pack-Erweiterung anzuzeigen, obwohl diese Form nur in abgeleiteten Klassendefinitionen sinnvoll ist. |
Using-Deklarationen können verwendet werden, um Namensraummitglieder in andere Namensräume und Block-Scopes einzuführen, oder um Basisklassenmitglieder in abgeleitete Klassendefinitionen einzuführen, oder um Enumerator in Namensräume, Block- und Klassenscopes einzuführen(seit C++20).
|
Eine Using-Deklaration mit mehr als einem Using-Deklarator ist äquivalent zu einer entsprechenden Sequenz von Using-Deklarationen mit einem Using-Deklarator. |
(seit C++17) |
Für die Verwendung in abgeleiteten Klassendefinitionen siehe Using-Deklaration.
In einen Namensraum-Scope eingeführte Namen durch eine Using-Deklaration können wie andere Namen verwendet werden, einschließlich qualifizierter Suche aus anderen Scopes.
void f(); namespace A { void g(); } namespace X { using ::f; // global f is now visible as ::X::f using A::g; // A::g is now visible as ::X::g using A::g, A::g; // (C++17) OK: double declaration allowed at namespace scope } void h() { X::f(); // calls ::f X::g(); // calls A::g }
Wenn nach der Verwendung einer Using-Deklaration, um ein Mitglied aus einem Namensraum zu übernehmen, der Namensraum erweitert und zusätzliche Deklarationen für denselben Namen eingeführt werden, werden diese zusätzlichen Deklarationen nicht über die Using-Deklaration sichtbar (im Gegensatz zur Using-Direktive). Eine Ausnahme bildet die Benennung einer Klassenvorlage durch eine Using-Deklaration: später eingeführte partielle Spezialisierungen sind effektiv sichtbar, da ihre Suche über die Primärvorlage erfolgt.
namespace A { void f(int); } using A::f; // ::f is now a synonym for A::f(int) namespace A // namespace extension { void f(char); // does not change what ::f means } void foo() { f('a'); // calls f(int), even though f(char) exists. } void bar() { using A::f; // this f is a synonym for both A::f(int) and A::f(char) f('a'); // calls f(char) }
Using-Deklarationen können keine template-id, keine Namensräume, und keine Scope-basierten Aufzählungen benennen(bis C++20). Jeder Deklarator in einer Using-Deklaration führt genau einen Namen ein, zum Beispiel führt eine Using-Deklaration für eine Aufzählung keine ihrer Aufzähler ein.
Alle Einschränkungen für reguläre Deklarationen derselben Namen, Hiding und Überladungsregeln gelten für Using-Deklarationen.
namespace A { int x; } namespace B { int i; struct g {}; struct x {}; void f(int); void f(double); void g(char); // OK: function name g hides struct g } void func() { int i; using B::i; // error: i declared twice void f(char); using B::f; // OK: f(char), f(int), f(double) are overloads f(3.5); // calls B::f(double) using B::g; g('a'); // calls B::g(char) struct g g1; // declares g1 to have type struct B::g using B::x; using A::x; // OK: hides struct B::x x = 99; // assigns to A::x struct x x1; // declares x1 to have type struct B::x }
Wenn eine Funktion durch eine Using-Deklaration eingeführt wurde, ist die Deklaration einer Funktion mit demselben Namen und derselben Parameterliste fehlerhaft (es sei denn, die Deklaration betrifft dieselbe Funktion). Wenn eine Funktionvorlage durch eine Using-Deklaration eingeführt wurde, ist die Deklaration einer Funktionvorlage mit demselben Namen, derselben Parameterliste, demselben Rückgabetyp und derselben Vorlagenparameterliste fehlerhaft. Zwei Using-Deklarationen können Funktionen mit demselben Namen und derselben Parameterliste einführen, aber wenn ein Aufruf dieser Funktion versucht wird, ist das Programm fehlerhaft.
namespace B { void f(int); void f(double); } namespace C { void f(int); void f(double); void f(char); } void h() { using B::f; // introduces B::f(int), B::f(double) using C::f; // introduces C::f(int), C::f(double), and C::f(char) f('h'); // calls C::f(char) f(1); // error: B::f(int) or C::f(int)? void f(int); // error: f(int) conflicts with C::f(int) and B::f(int) }
Wenn eine Entität in einem inneren Namensraum deklariert, aber nicht definiert ist, und dann durch eine Using-Deklaration im äußeren Namensraum deklariert wird, und dann eine Definition im äußeren Namensraum mit demselben nicht-qualifizierten Namen erscheint, ist diese Definition ein Mitglied des äußeren Namensraums und steht im Konflikt mit der Using-Deklaration.
namespace X { namespace M { void g(); // declares, but doesn't define X::M::g() } using M::g; void g(); // Error: attempt to declare X::g which conflicts with X::M::g() }
Allgemeiner gesagt, führt eine Deklaration, die in einem Namensraum-Scope erscheint und einen Namen mittels eines nicht-qualifizierten Identifikators einführt, immer ein Mitglied in den Namensraum ein, in dem sie sich befindet, und nicht in einen anderen Namensraum. Ausnahmen bilden explizite Instanziierungen und explizite Spezialisierungen einer Primärvorlage, die in einem Inline-Namensraum definiert ist: da sie keinen neuen Namen einführen, dürfen sie unqualified-id in einem umgebenden Namensraum verwenden.
[bearbeiten] Using-Direktiven
Eine using-directive ist eine block-declaration mit der folgenden Syntax
attr (optional) using namespace nested-name-specifier (optional) namespace-name ; |
(1) | ||||||||
| attr | - | (seit C++11) beliebig viele Attribute, die für diese Using-Direktive gelten. |
| nested-name-specifier | - | eine Sequenz von Namen und Scope-Auflösungsoperatoren ::, die mit einem Scope-Auflösungsoperator endet. Ein einzelnes :: bezieht sich auf den globalen Namensraum. Bei der Suche nach den Namen in dieser Sequenz berücksichtigt die Suche nur Namensraumdeklarationen. |
| namespace-name | - | ein Name eines Namensraums. Bei der Suche nach diesem Namen berücksichtigt die Suche nur Namensraumdeklarationen. |
Using-Direktiven sind nur im Namensraum- Scope und im Block-Scope erlaubt. Aus der Sicht der nicht-qualifizierten Namenssuche für jeden Namen nach einer Using-Direktive und bis zum Ende des Scopes, in dem sie erscheint, ist jeder Name aus namespace-name sichtbar, als ob er im nächstgelegenen umgebenden Namensraum deklariert worden wäre, der sowohl die Using-Direktive als auch namespace-name enthält.
Eine Using-Direktive fügt dem Deklarationsbereich, in dem sie erscheint, keine Namen hinzu (im Gegensatz zur Using-Deklaration) und verhindert somit nicht, dass identische Namen deklariert werden.
Using-Direktiven sind für die Zwecke der nicht-qualifizierten Suche transitiv: Wenn ein Scope eine Using-Direktive enthält, die einen namespace-name nominiert, der selbst Using-Direktiven für einen bestimmten namespace-name-2 enthält, ist die Wirkung so, als ob die Using-Direktiven aus dem zweiten Namensraum innerhalb des ersten erscheinen würden. Die Reihenfolge, in der diese transitiven Namensräume auftreten, beeinflusst die Namenssuche nicht.
namespace A { int i; } namespace B { int i; int j; namespace C { namespace D { using namespace A; // Names from A are "injected" into D. // Unqualified lookup within D considers these names to have the same // scope as the global scope (e.g. for the purposes of name hiding). // Qualified lookup referring to D (D::name for some name) // will find the same name as unqualified lookup within D. int j; int k; int a = i; // i is B::i, because A::i is hidden by B::i int b = ::i; // error: there is still no i in the global namespace } using namespace D; // names from D and A are injected into C int k = 89; // OK to declare name identical to one introduced by a using int l = k; // ambiguous: C::k or D::k int m = i; // ok: B::i hides A::i int n = j; // ok: D::j hides B::j } } // These are all equivalent definitions: int t0 = B::i; int t1 = B::C::a; int t2 = B::C::D::a;
Wenn nach der Verwendung einer Using-Direktive zur Nominierung eines Namensraums der Namensraum erweitert und zusätzliche Mitglieder und/oder Using-Direktiven hinzugefügt werden, sind diese zusätzlichen Mitglieder und die zusätzlichen Namensräume über die Using-Direktive sichtbar (im Gegensatz zur Using-Deklaration).
namespace D { int d1; void f(char); } using namespace D; // introduces D::d1, D::f, D::d2, D::f, // E::e, and E::f into global namespace! int d1; // OK: no conflict with D::d1 when declaring namespace E { int e; void f(int); } namespace D // namespace extension { int d2; using namespace E; // transitive using-directive void f(int); } void f() { d1++; // error: ambiguous ::d1 or D::d1? ::d1++; // OK D::d1++; // OK d2++; // OK, d2 is D::d2 e++; // OK: e is E::e due to transitive using f(1); // error: ambiguous: D::f(int) or E::f(int)? f('a'); // OK: the only f(char) is D::f(char) }
[bearbeiten] Notizen
Die Using-Direktive using namespace std; in einem beliebigen Namensraum-Scope führt jeden Namen aus dem Namensraum std in den globalen Namensraum ein (da der globale Namensraum der nächstgelegene Namensraum ist, der sowohl std als auch jeden benutzerdefinierten Namensraum enthält), was zu unerwünschten Namenskollisionen führen kann. Dies und andere Using-Direktiven gelten im Allgemeinen als schlechte Praxis im Dateiscope einer Header-Datei (SF.7: Schreiben Sie keine using namespace im globalen Scope einer Header-Datei).
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_namespace_attributes |
201411L |
(C++17) | Attribute für Namensräume |
[bearbeiten] Schlüsselwörter
[bearbeiten] Beispiel
Dieses Beispiel zeigt, wie ein Namensraum verwendet wird, um eine Klasse zu erstellen, die bereits im Namensraum std benannt wurde.
#include <vector> namespace vec { template<typename T> class vector { // ... }; } // of vec int main() { std::vector<int> v1; // Standard vector. vec::vector<int> v2; // User defined vector. // v1 = v2; // Error: v1 and v2 are different object's type. { using namespace std; vector<int> v3; // Same as std::vector v1 = v3; // OK } { using vec::vector; vector<int> v4; // Same as vec::vector v2 = v4; // OK } }
[bearbeiten] Berichte über Fehler
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 101 | C++98 | das Programm ist fehlerhaft, wenn eine Funktionsdeklaration im Namensraum Scope oder Block-Scope und eine Funktion, die durch eine Using-Deklaration eingeführt wurde, dieselbe Funktion deklarieren (keine Mehrdeutigkeit). |
erlaubt |
| CWG 373 | C++98 | die Suche berücksichtigte nur Namensraumdeklarationen nur für den letzten Namen im Operanden einer Using-Direktive (was suboptimal ist, da Klassen keine Namensräume enthalten können). |
die Suchbeschränkung gilt für alle Namen in den Operanden von Using-Direktiven. |
| CWG 460 | C++98 | eine Using-Deklaration konnte einen Namensraum benennen. | verboten |
| CWG 565 | C++98 | eine Using-Deklaration kann keine Funktion einführen, die identisch mit einer anderen Funktion im selben Scope ist, aber die Einschränkung wurde nicht auf Funktionsvorlagen angewendet. |
die gleiche Einschränkung anwenden auf Funktionsvorlagen ebenso. |
| CWG 986 | C++98 | die Using-Direktive war transitiv für die qualifizierte Suche | nur transitiv für die nicht-qualifizierte Suche |
| CWG 987 | C++98 | Entitäten, die in einem verschachtelten Namensraum deklariert wurden, waren auch Mitglieder des umgebenden Namensraums. |
verschachtelte Scopes ausgeschlossen |
| CWG 1021 | C++98 | Es war unklar, ob eine Entität, deren Definition über eine Using-Deklaration in einen Namensraum eingeführt wurde, als in diesem Namensraum definiert gilt. |
nicht im Namensraum definiert |
| CWG 1838 | C++98 | eine nicht-qualifizierte Definition in einem äußeren Namensraum konnte eine Entität definieren, die in einem anderen Namensraum deklariert, aber nicht definiert und durch ein Using |
nicht-qualifizierte Definition bezieht sich immer auf ihren Namensraum. |
| CWG 2155 | C++98 | die Auflösung von CWG-Problem 1838 wurde nicht auf Klassen- und Aufzählungsdeklarationen angewendet. |
angewendet |
[bearbeiten] Siehe auch
| Namensraum-Alias | erstellt einen Alias für einen bestehenden Namensraum |