Alle Namen, die außerhalb sämtlicher Blöcke oder Klassen deklariert werden, sind globale Namen (vgl. Abschnitt 10.1); sie liegen im globalen Namensbereich. Hier können sich Probleme ergeben, wenn man gleichzeitig mehrere Klassenbibliotheken einsetzt und diese dieselben Namen zur Bezeichnung ihrer Klassen, Funktionen oder Konstanten verwenden. Die auftretenden Namenskonflikte können von Benutzern, denen die Bibliotheken typischerweise nur in übersetzter Form vorliegen, dann nicht gelöst werden.
In einem Programm soll aus einer Klassenbibliothek A eine Folge
und aus
der Bibliothek B ein Baum
wiederverwendet werden. Beide Bibliotheken
definieren für ihre Zwecke eine "allgemeine" Basisklasse Objekt
,
von der alle Klassen abzuleiten sind, die in der Folge verwaltet werden sollen bzw. als
Knoten in einem Baum enthalten sein können. In der Header-Datei a.h
der
Bibliothek A befinden sich u.a. die beiden Definitionen
class Objekt { /* ... */ }; struct Folge { void fuegeEin(Objekt*); struct Element { // ... Objekt* obj; }; Element* erstes; // ... };
und in b.h
, der Header-Datei der Bibliothek B, findet man die Definitionen
class Objekt { /* ... */ }; class Baum { // ... struct Knoten { // ... Objekt* zgrO; }; Knoten* wurzel; public: void balanciere(); };
Der Versuch, beide Bibliotheken gemeinsam, z.B. wie folgt, in einem Programm zu benutzen, schlägt jedoch fehl:
#include "a.h" #include "b.h" Folge f; Baum b;
Da beide Bibliotheken denselben Namen für die allgemeine Basisklasse verwenden, gibt
es für die Klasse Objekt
zwei Definitionen, und das Programm kann nicht
übersetzt werden. Wenn nicht für mindestens eine der Bibliotheken die C++-Quellen
verfügbar sind, besteht keine Möglichkeit, das aufgetretene Problem durch Umbenennen
einer der Klassen zu lösen. Der herkömmliche Versuch, solchen Fehlern von seiten der
Bibliothekshersteller vorzubeugen, ist das Versehen der Klassennamen mit einem Präfix,
z.B. AObjekt
, AFolge
, BObjekt
, BBaum
usw. Bei kurzen Vorsilben wie A
oder B
treten die genannten
Probleme u.U. früher oder später wieder auf. Und sehr lange Klassennamen, z.B. OOTechGraphenUndNetzeBibBaum
,
sind unpraktisch und verschleiern die Klassennamen. Die beste Problemlösung liefern
Namensbereiche, in denen Deklarationen zusammengefaßt werden können, die dann nicht mehr
zum globalen Namensbereich gehören, sondern einen eigenständigen Geltungsbereich bilden.
Die Definition eines Namensbereichs wird mit dem Schlüsselwort namespace
eingeleitet und muß global (oder in einem anderen Namensbereich) erfolgen. Soll der
Namensbereich benannt werden, folgt vor der Aufzählung seiner Elemente ein Bezeichner.
Namespace-Definition:
namespace
Bezeichneropt { Deklarationsfolgeopt }
Zwischen den Klammern {
und }
werden die Elemente des
Namensbereichs deklariert. Zum Beispiel wird mit
namespace A { class Objekt { /* ... */ }; class Folge { /* ... */ }; }
ein Namensbereich A
mit den beiden Elementen Objekt
und Folge
definiert. Im Unterschied zu anderen Geltungsbereichen kann ein Namensbereich innerhalb
einer Programmdatei in mehrere zusammengehörende Teile zerlegt werden. Die einzelnen
Teile müssen sich dann alle im selben (z.B. globalen) Geltungsbereich befinden. Zum
Beispiel
namespace B { class Objekt { /* ... */ }; // Teil 1 } // ... namespace B { class Baum { /* ... */ }; // Teil 2 }
Hier wird der Namensbereich B
mit den beiden Elementen Objekt
und Baum
in zwei Teilen definiert. Ein Namensbereich kann somit auch auf
mehrere Header-Dateien verteilt werden. Auf die Namen der Elemente eines Namensbereichs
kann vollständig qualifiziert zugegriffen werden:
A::Folge f; B::Baum b;
Gleiche Namen in unterschiedlichen Namensbereichen führen nicht zu Mehrdeutigkeiten,
da sie durch ihre Qualifizierung unterscheidbar sind wie z.B. die Klassen A::Objekt
und B::Objekt
. Bemerkungen:
namespace X { namespace Y { void f(); } }
Der Zugriff erfolgt hier - analog zum Zugriff auf eingebettete Klassen - vollständig
qualifiziert mittels X::Y::f()
.
::
zugegriffen werden (vgl.
Abschnitt 10.1). int fehlerCode; class X { // ... void fehler(); }; void X::fehler() { if (err == 0) ::fehlerCode = 0; else if (err > 0) { /* ... */ } else { /* ... */ } }
static
, inline
oder const
interne Bindung hergestellt werden (vgl. Kapitel 12). Die Elemente eines (benannten oder unbenannten) Namensbereichs können innerhalb des Namensbereichs nicht nur deklariert sondern auch definiert werden:
namespace X { void f() { /* ... */ } }
In diesem Fall darf sich die Definition des Namensbereichs nicht in einer Header-Datei
befinden, die mehrfach in ein Programm eingefügt wird. Von dieser Einschränkung sind
Definitionen von Klassen, inline
-Funktionen, Aufzählungstypen und Konstanten
mit interner Bindung ausgenommen, da sie in jede Programmdatei (höchstens) einmal
aufgenommen werden können (vgl. beispielsweise S. 213). Die Elemente eines benannten
Namensbereichs können auch außerhalb des Namensbereichs definiert werden, wenn ihr Name
mit dem Namen des Namensbereichs qualifiziert wird.
namespace T { enum tag { Mo, Di, Mi, Do, Fr, Sa, So }; tag naechsterTag(tag); } T::tag T::naechsterTag(tag t) { return (t == So) ? Mo : static_cast<tag>(t + 1); }
Wie im Beispiel dargestellt, können Elemente des Namensbereichs in einer Definition
ohne Qualifizierung angesprochen werden, nachdem klar ist, zu welchem Namensbereich die
Definition gehört. Ähnlich wie bei der Qualifizierung der Namen von Elementfunktionen in
Abschnitt 14.3 wird auch die Definition eines Namensbereichs-Elements durch die Verwendung
des Geltungsbereichoperators in den Geltungsbereich ihres Namensbereichs aufgenommen. Im
folgenden Beispiel ist der Geltungsbereich des Namensbereichs Y
dunkler
schattiert dargestellt als der von X
.
Wenn ein benannter Namensbereich ausschließlich Deklarationen, Klassendefinitionen, inline
-Funktionen,
Aufzählungstypen oder Konstanten enthält, kann er in einer Header-Datei stehen, die
mehrfach in ein Programm eingebunden wird. Z.B.
// Header-Datei n.h #ifndef _N_H #define _N_H #include <iostream.h> namespace A { class X { public: X(int = 0); int xWert() const; private: int x; }; extern int i; inline int f() { return i*(i + 1)/2; } } #endif
Die noch fehlenden Definitionen werden in diesem Fall - analog zur Definition von
Elementfunktionen und static
Datenelementen einer Klasse - am besten in eine
eigene Datei (n.cpp
) aufgenommen, die getrennt übersetzbar ist. Zum Beispiel
#include "n.h" namespace A { X::X(int n) { x = n; } int X::xWert() const { return x; } int i = 10; }
Hier haben wir von der Möglichkeit Gebrauch gemacht, den Namensbereich fortzusetzen. Mit vollständiger Qualifizierung kann man dieselbe Wirkung erzielen:
#include "n.h" A::X::X(int n) { x = n; } int A::X::xWert() const { return x; } int A::i = 10;
In prog-23
wird der Namensbereich A
genutzt, indem dort (wie
üblich) die Header-Datei eingefügt wird und die Datei mit den bereits übersetzen
Definitionen hinzugebunden wird.
// prog-23 #include "n.h" int main() { int j = 10*A::f(); A::X feld[3] = { A::X(j), A::X(A::i), A::X() }; A::X* z = feld; for (int i = 0; i < 3; i++) cout << z++->xWert() << endl; return 0; }
// f() und h() noch nicht deklariert namespace X { class C { friend void f(); // X::f }; void g() { extern void h(); // X::h } }
Bei sehr kurzen Bezeichnern von Namensbereichen, wie z.B. A
und B
für das Problem aus Abschnitt 19.1, besteht die
Gefahr, daß durch die Verwendung gleicher Namensbereich- und Elementnamen die alten
Namenskonflikte erneut auftreten. Bei der Implementation von Klassenbibliotheken wird man
daher i.d.R. sehr lange Bezeichner für Namensbereiche wählen. Zur Vereinfachung des
Zugriffs auf die Elemente dieser Namensbereiche können von den Anwendern auch Aliasnamen
für Namensbereiche definiert werden:
Namespace-Alias-Definition:
namespace
Bezeichner=
Namespace-Name;
Namespace-Name:
::
opt Bezeichner
Namespace-Name
::
Bezeichner
Damit ist es möglich, lange Bezeichner oder mehrfach qualifizierte Namen durch ein kürzeres Synonym zu ersetzen. Zum Beispiel
namespace OOTechGraphenUndNetzeBib { class Baum { /* ... */ }; class BinaerBaum { /* ... */ }; class Wald { /* ... */ }; // ... } namespace GUN = OOTechGraphenUndNetzeBib; GUN::Wald w1; // statt: OOTechGraphenUndNetzeBib::Wald w1; namespace XYZ = Xnsp::Ynsp::Znsp;
Ebenso wie die Vergabe von Aliasnamen für Namensbereiche verfolgt die im folgenden
Abschnitt erläuterte using
-Deklaration das Ziel, den Zugriff auf die
Elemente eines Namensbereichs zu vereinfachen.
using
-DeklarationenMit einer using
-Deklaration wird ein Name aus einem Namensbereich
in den Geltungsbereich eingeführt, in dem die using
-Deklaration erfolgt.
Using-Deklaration:
using
Namespace-Name::
Bezeichner;
Das bezeichnete Element wird dadurch deklariert; dabei handelt es sich um eine
"normale" Deklaration, mit der man eine anderorts definierte Größe in einen
Geltungsbereich einbringt. Typ, Bindung oder andere Eigenschaften des
Namensbereich-Elements werden nicht beeinflußt. Sein Name kann jedoch anschließend ohne
Qualifizierung benutzt werden - dies ist vorteilhaft, wenn viele Zugriffe nötig sind. Die
Funktion main()
aus prog-23
kann man beispielsweise auch so implementieren:
int j = 10*A::f(); using A::X; X feld[3] = { X(j), X(A::i), X() }; X* z = feld; // ...
Auf Elemente, die mittels einer using
-Deklaration innerhalb eines anderen
Namensbereichs deklariert wurden, kann ebenso wie auf die übrigen Elemente dieses
Namensbereichs vollständig qualifiziert zugegriffen werden. Zum Beispiel
namespace A { void g(); } namespace B { void f(); using A::g; // g() aus A } void test() { B::f(); // ruft f() aus B auf B::g(); // ruft g() aus A auf }
Bei solchen Konstruktionen ist nicht mehr auf Anhieb erkennbar, welches Element gemeint
ist. Wenn innerhalb eines Geltungsbereichs eine Menge von lokalen Deklarationen und using
-Deklarationen
für denselben Namen angegeben ist, so müssen sich diese entweder auf ein und dasselbe
Objekt oder auf überladene Funktionen beziehen:
namespace A { int i; int h(int); int h(double); } void g() { int i; using A::i; // Fehler int h(char); using A::h; // ... }
Hier ist die using
-Deklaration von A::i
ein Fehler, weil in g()
bereits eine lokale Variable i
deklariert ist. Nach using A::h
sind A::h(int)
und A::h(double)
in g()
ohne
Qualifizierung aufrufbar.
using
-DirektivenDurch eine using
-Direktive können sämtliche Namen des angegebenen
Namensbereichs für den Geltungsbereich zugreifbar gemacht werden, in dem die using
-
Direktive enthalten ist.
Using-Direktive:
using
namespace
Namespace-Name;
Der Zugriff kann anschließend ohne Qualifizierung erfolgen. Die using
-Direktive
wirkt sich dabei so aus, als seien alle Elemente außerhalb ihres Namensbereichs
deklariert, und zwar an der Stelle, an der die Namensbereich-Definition tatsächlich
steht. Zum Beispiel haben die beiden folgenden Programmfragmente dieselbe Wirkung.
namespace N { class X { /* ... */ }; void f(); } void test() { using namespace N; X x; f(); } |
class X { /* ... */ }; void f(); void test() { X x; f(); } |
Eine using
-Direktive führt keine neuen Namen in den Geltungsbereich ein,
in dem sie sich befindet. Darin unterscheidet sie sich von der using
-Deklaration
(vgl. Übungsaufgabe 1). using
-Direktiven
sind insbesondere zur Sicherung der Kompatibilität in der Übergangsphase der
C++-Programmierung zur Standardverwendung von Namensbereichen bei der Entwicklung von
Klassenbibliotheken sinnvoll einsetzbar. Zum Beispiel ist ein "altes"
Anwendungsprogramm
#include "liste.h" int main() { Liste l; // ... return 0; }
auch dann noch unverändert übersetzbar, wenn die Header- Datei mit der
Klassendefinition der Liste
und die Datei mit den Definitionen der
Elementfunktionen bei Überarbeitungen in einen Namensbereich aufgenommen werden - sofern
die Header-Datei mit einer entsprechenden using
-Direktive abgeschlossen wird:
// Header-Datei: liste.h namespace OOTechContainerBib { class Liste { /* ... */ }; } using namespace OOTechContainerBib;
Auch die C++-Standardbibliothek macht von dieser Technik Gebrauch, indem die alten
Header-Dateien (mit Endung .h
) die neuen (ohne .h
) einbinden und
die using
-Direktive using namespace std;
ergänzen (vgl.
Abschnitt 2.7).
std
Im zukünftigen Sprachstandard werden sämtliche Bezeichner der C++-Standardbibliothek
zum Namensbereich std
gehören. Die Deklarationen befinden sich in den neuen
Header- Dateien. Das elementare "Hello, world!"
-Programm wird damit
zu:
#include <iostream> int main() { std::cout << "Hello, world!" << std::endl; return 0; }
Sofern Bezeichner der Standardbibliothek nicht anderweitig benutzt werden - was aus
Gründen der Lesbarkeit generell zu empfehlen ist - ist der Einsatz einer using
-Direktive
auch in einem Anwendungsprogramm gerechtfertigt, weil durch die vollständige
Qualifizierung kein Vorteil erzielt wird.
#include <iostream> int main() { using namespace std; cout << "Hello, world!" << endl; return 0; }
Der Bezeichner in einer Namensbereich-Definition ist optional, d.h. es ist möglich, einen unbenannten Namensbereich zu definieren, z.B.
namespace { int i; double x; bool c(char, char); }
Eine derartige Definition wird intern durch folgende Konstruktion realisiert:
namespace EindeutigerBezeichner { int i; double x; bool c(char, char); } using namespace EindeutigerBezeichner;
wobei EindeutigerBezeichner
ein vom Compiler vergebener Bezeichner ist,
der sich von allen anderen Bezeichnern des Programms unterscheidet. Alle unbenannten
Namensbereiche einer Programmdatei werden zu "dem" unbenannten Namensbereich
dieser Datei zusammengefaßt. (Der interne Bezeichner dieses Namensbereichs enthält
i.d.R. den Namen seiner Programmdatei.) Aufgrund der obigen Konstruktion und weil kein
Name zur Qualifizierung vorhanden ist, wird auf die Elemente des unbenannten
Namensbereichs ohne Qualifizierung zugegriffen. Die Elemente des unbenannten
Namensbereichs sind somit nur in ihrer jeweiligen Programmdatei zugreifbar, auch wenn sie
externe Bindung haben. Unbenannte Namensbereiche bieten hier also eine Alternative zur
Verwendung des Schlüsselworts static
, wenn die Sichtbarkeit von globalen
Größen (Variablen, Funktionen usw.) auf eine einzelne Programmdatei eingeschränkt
werden soll. (Vgl. hierzu die Anmerkung auf S. 138.)
In Ergänzung zu den Herleitungen aus Kapitel 12 können wir jetzt den Unterschied zwischen Namen mit interner Bindung und Namen ohne Bindung besser durch Beispiele veranschaulichen. Einen Namen ohne Bindung kann man nur in seinem Geltungsbereich (und nur innerhalb seiner Programmdatei) verwenden; ein Zugriff aus einem anderen Geltungsbereich ist nicht möglich. Zum Beispiel ist
void f(int); int main() { for (int i = 0; i < 10; ++i) f(i); return 0; } void f(int j) { i += j; // Fehler: kein Zugriff auf i }
fehlerhaft, da die Variable i
lokal für die for
-Anweisung in
main()
ist und man in f()
nicht auf sie zugreifen kann. Dagegen
kann man auf Namen mit interner Bindung auch aus anderen Geltungsbereichen derselben
Programmdatei zugreifen, wie es das folgende Beispiel für die Namen f
und a
zeigt:
namespace X { inline void f(int i) { cout << "X::f(" << i << ')' << endl; } const int a = -707; // f und a haben interne Bindung } void g(int i) { X::f(i); // Zugriff aus anderem Geltungsbereich } int main() { int i = X::a; // Zugriff aus anderem Geltungsbereich g(i); return 0; }
Und auf Namen mit externer Bindung kann auch aus anderen Geltungsbereichen anderer Programmdateien zugreifen; siehe die Beispiele in Kapitel 12.
Weshalb ist die Einführung sämtlicher Namen eines
Namensbereichs mit using
-Deklarationen nicht äquivalent zu der Angabe einer using
-Direktive?
Vergleichen Sie, wie ihr Compiler auf die folgenden beiden Programme reagiert.
namespace X { int i; double x; } int main() { int i = 1; X::i = 10; using X::i; using X::x; cout << i; return 0; } |
namespace X { int i; double x; } int main() { int i = 1; X::i = 10; using namespace X; cout << i; return 0; } |
static
unbenannte
Namensbereiche. #include <iostream.h> namespace T { enum tag { Mo, Di, Mi, Do, Fr, Sa, So }; tag naechsterTag(tag); // Definition wie oben } void fkt() { tag x = Fr; x = naechsterTag(x); cout << x << endl; } int main() { fkt(); return 0; }