Einleitung
Definition eines Namensraums
Anwendung von Namensräumen
Zugriff auf Namensräume
Geschachtelte und anonyme Namensräume
In dieser Lektion wollen wir uns mit Namensräume befassen und damit nun auch endlich die Anweisung using xxx; näher betrachten.
Namensräume wurden dazu geschaffen, Namenskonflikte von Variablen, Klassen usw. aufzulösen. Auch diese Problematik lässt sich wiederum am Besten anhand eines Beispiels demonstrieren.
Im Beispiel wird eine Variable var und eine Klasse Any definiert. Auf beide wird in main() zugegriffen. Bis jetzt also noch nichts Unbekanntes. Worauf es im Folgenden ankommt sind die Namen.
// Definitionen int var; class Any {....}; // main() Funktion int main() { Any myObj; var = 10; .... } |
Jetzt wollen Sie Ihr Programm erweitern und setzen dazu z.B. eine gekaufte oder frei verfügbare Klassenbibliothek ein. Zu dieser Klassenbibliothek gehört natürlich auch eine Header-Datei (im Beispiel clib.h), die auszugsweise unten dargestellt ist. In dieser Header-Datei ist aber unglücklicherweise ebenfalls eine Klasse Any und eine Variable var definiert. Was nun kommt ahnen Sie vielleicht schon.
// Headerdatei der gekauften oder frei verfügbaren Klassenbibliothek clib
#ifndef CLIB_H
#define CLIB_H
int var;
class Any
{....};
#endif
|
Um die Klassenbibliothek in Ihrem Programm verwenden zu können, müssen Sie selbstverständlich die Header-Datei clib.h einbinden. Und damit können Sie das Programm nicht mehr fehlerfrei übersetzen, da sowohl in der Header-Datei clib.h wie auch in Ihrem Programm die Variable var und die Klasse Any definiert sind. Ein erster Lösungsansatz bestünde nun darin, dass Sie in Ihrem Programm die Namenskonflikte durch entsprechende Umbenennungen auflösen. Dies kann aber je nach Programmgröße sehr umfangreich, und was noch wichtiger ist, auch fehleranfällig sein. Vergessen Sie z.B. nur ein einziges Vorkommen von var in Ihrem Programm umzubenennen, dann rechnen Sie fast unbemerkt mit einer 'falschen' Variable weiter.
#include "clib.h"
// Definitionen int var; // Das erzeugt jetzt Fehler! class Any {....}; // main() Funktion int main() { Any myObj; var = 10; .... } |
Und genau an dieser Stelle kommen die Namensräume ins Spiel. Die allgemeine Syntax zur Definition eines Namensraums lautet:
| namespace [NNAME] { .... // Definitionen und Deklarationen } |
NNAME gibt den Namen des Namensraums an. Und alle in einem Namensraum vorgenommenen Definitionen und Deklarationen sind auch nur innerhalb dieses Namensraums gültig! Gleichnamige Definitionen/Deklarationen in unterschiedlichen Namensräumen erzeugen somit keinen Namenskonflikt mehr.
Um den Namenskonflikt mithilfe von Namensräume in unserem Beispiel aufzulösen, stehen zwei Lösungswege zur Verfügung. Im ersten Fall legen wir die Header-Datei clib.h in einen eigenen Namensraum und im zweiten Fall unsere eigenen Definitionen und Deklarationen. Beide Lösungen sind gleichwertig. Da nun in beiden Fällen die Definitionen von var und Any in verschiedenen Namensräumen erfolgen, gibt es keinen Namenskonflikt mehr. Auf den Zugriff auf die Klasse Any bzw. auf die Variable var kommen wir gleich zu sprechen.
Lösung 1: namespace clib { #include "clib.h" } int var; class Any {....}; Lösung 2: #include "clib.h" namespace cppkurs { int var; class Any {....}; } |
Die Anzahl der Namensräume innerhalb eines Programms ist nicht begrenzt.
Und noch ein Hinweis: Kommen Sie hier nicht auf die Idee, in die käuflich erworbene Header-Datei clib.h einen Namensraum einzubauen. Beim nächsten Update der Bibliothek (mit neuer clib.h-Datei) wäre der Namensraum dann wieder verschwunden!
Wird ein bereits bestehender Namensraum erneut definiert (geöffnet), so wird der bisherige Namensraum um die Deklarationen und Definitionen im erneuten Namensraum erweitert. Im Beispiel enthält der Namensraum cppkurs alle Deklarationen und Definitionen aus den beiden Header-Dateien mylib.h und yourlib.h.
// Header-Datei mylib.h namespace cppkurs { ... } // Header-Datei yourlib.h namespace cppkurs { ... } |
So, sehen wir uns jetzt die Zugriffe auf die Namensräume an. Hierzu gibt es ebenfalls verschiedene Wege. Eine Lösung besteht darin, dass beim Zugriff auf die Deklarationen und Definitionen aus einem Namenraum zuerst der jeweilige Namensraum, dann der Zugriffsoperator :: und danach die entsprechende Deklaration/Definition angegeben wird. Im Beispiel wurde die Header-Datei clib.h in einen eigenen Namensraum clib gelegt. Wie Sie dann auf die darin enthaltene Variable var zugreifen und ein Objekt der darin enthaltenen Klasse Any definieren ist ebenfalls dargestellt.
namespace clib { #include "clib.h" } int var; // Eigene Definitionen class Any // und Deklarationen {....}; // Zugriff auf var aus Namensraum clib clib::var = 10; // Definition eines Objekts vom Typ Any aus dem Namensraum clib clib::Any myObj; // Zugriff auf eigenes var var = 33; |
Eine weitere Lösung für den Zugriff besteht darin, den Namensraum einer Variablen, Funktion, Objekts usw. explizit für die restlichen Anweisungen des aktuellen Blocks vorzugeben. Dies erfolgt mittels des Schlüsselworts using.
| using NNAME::MNAME; |
NNAME ist der Name des Namensraums und MNAME der Name der jeweiligen Deklaration/Definition. Die using-Anweisung erlaubt aber nur die Angabe eines Members. Sollen mehrere Member aus einem Namenraum explizit vorgegeben werden, so müssen entsprechend mehrere using-Anweisungen hierfür verwendet werden.
// Für Any standardmäßig clib-Namensraum setzen using clib::Any; // Objekt aus clib-Namensraum definieren Any myObj; // Objekt aus eignem Namensraum definieren ::Any anotherObj; // Standard var ist aus clib Namensraum using clib::var; var = 10; // Nun var aus globalem Namensraum setzen ::var = -10; |
Und noch einmal weil's so wichtig ist: Die Gültigkeit einer using-Anweisung endet am Ende des aktuellen Blocks {...}!
Sollen alle Member eines Namensraums eingeblendet werden, so wird dafür eine etwas andere Form der using-Anweisung verwendet:
| using namespace NNAME; |
NNAME ist wiederum der Name des einzublendenden Namensraums. Schreiben Sie im Programm z.B. using namespace std; so wird der komplette Namensraum std eingeblendet, mit allen Vor- und Nachteilen.
Sie können in einem Programm auch mehrere Namensräume mittels using namespace einblenden. Diese Namensräume sind dann additiv, d.h. es sind alle Member aus den Namenräumen gültig. Soll dieses additive Verhalten vermieden werden, so setzen Sie die using-Anweisung einfach in einen Block {...}. Wie bereits vorhin erwähnt, endet das Einblenden eines Namensraums immer an der Grenze des aktuellen Blocks.
using namespace std;
using namespace cppkurs;
// Ab hier können alle Member des Namensraums
// std und cppkurs direkt verwendet werden
....
|
Ebenfalls möglich, wenn auch selten angewandt, ist es, Namenräume zu schachteln. Beim expliziten Zugriff auf ein Member müssen dann die Namen beider Namenräume (von außen nach innen) angegeben werden.
namespace outer { namespace inner { int var; } } // Expliziter Zugriff auf var outer::inner::var = 10; |
Mit Hilfe eines Namensraums können Sie auch Variablen, Funktionen usw. einer 'Übersetzungseinheit' (in der Regel ist dies eine Quelldatei) nach außen hin verbergen. Innerhalb der Übersetzungseinheit kann dann auf alle Member des Namensraums wie gewohnt zugegriffen werden, aber außerhalb der Übersetzungseinheit haben Sie keinen Zugriff auf den Inhalt des Namensraums. So verhält sich im Beispiel die Variable var innerhalb der Quelldatei wie eine globale Variable, aber nach außen hin ist sie nicht sichtbar. Würde in einer anderen Quelldatei z.B. die Anweisung extern int var stehen, so würde dies beim Linken des Programms zu einem Fehler führen.
namespace
{
int var;
....
void Func1(...)
{
var = 10;
....
}
....
}
|
Das obige Beispiel enthält noch eine kleine Besonderheit, den unbenannten oder anonymen Namensraum. Da auch nur innerhalb des Namensraums auf die Member des Namensraums zugegriffen werden soll, benötigen Sie hier keinen Namen für den Namensraum.
So, und damit hätten wir diese Lektion fast beendet, es fehlt nur noch ein winziger Hinweis: Namensräume dürfen nicht innerhalb von Blöcken definiert werden. Die einzige Ausnahme dabei bilden die vorhin erwähnten geschachtelten Namensräume.
// Das geht nicht!
...
void Func(...)
{
namespace wrong
{
....
}
}
|
So und das war's auch schon. Dieses mal ohne Beispiel und ohne Übung.