Namensraumdefinitionen in C++
Ein Namensraum enthält Definitionen, deren Namen eine Menge bilden. Ein Namensraum kann selber einen Namen haben.
Die Definition von Namen innerhalb eines Namensraumes kann über mehrere Übersetzungseinheiten verteilt sein.
Die folgende Produktionsregel zeigt den Aufbau der ersten Definition eines Namensraums innerhalb einer Übersetzungseinheit.
- 〈original-namespace-definition 〉 ::=
- "namespace" 〈identifier 〉 "{" 〈namespace-body 〉 "}".
- 〈namespace-body 〉 ::=
- [〈declaration-seq 〉].
Der Rumpf 〈namespace-body 〉 kann eine Sequenz von Deklarationen enthalten. Die dort definierten Bezeichner sind dann direkt in diesem Namensraum enthalten (wenn sie nicht selber Bestandteil weiter innen liegender Gültigkeitsbereiche [Blöcke, Namensräume oder Klassen] sind).
Der Aufbau weiterer Definitionen, die denselben Namensraum erweitern, gehorcht demselben Schema 〈original-namespace-definition 〉, nur daß ein in der Übersetzungseinheit schon zuvor als Namensraum definierter Name 〈identifier 〉 darin vorkommt.
Ein Name, der nicht in einem Namensraum, einem Block oder einer Klasse definiert wird hat den Globalnamensraum-Gültigkeitsbereich (auch globaler Gültigkeitsbereich genannt), die darin definierten Bezeichner werden auch globale Bezeichner genannt.
Eine Namensraumdefinition ist nur im globalen Gültigkeitsbereich oder in einem anderen Namensraum möglich.
Definitionen von Bezeichnern eines Namensraumes können in verschiedenen Übersetzungseinheiten enthalten sein.
Eine Übersetzungseinheit kann Definitionen von Bezeichnern in verschiedenen Namensräumen enthalten.
Anwendungsbeispiel
Ein gemeinsamer Namensraum "::lied" kann Klassen zu Liedern enthalten, ähnlich wie ein Buch, das verschiedene Lieder enthält. Dadurch wird ihre Zusammengehörigkeit klargestellt und Namenskollisionen mit gleichnamigen Klassen anderer Art werden unwahrscheinlicher. Die Klasse "kuckuck" im Namensraum "::lied" wird dann mit dem id-Ausdruck "::lied::kuckuck" bezeichnet.
Das folgende Anwendungsbeispiel geht von einem Ordnungssystem aus, in dem eine Datei Bezeichner in höchstens dem benannten Namensraum "::lied" definiert und gegebenenfalls in einem Unterverzeichnis enthalten ist, das nach einem solchen Namensraum benannt ist. (Auf Systemen ohne benannte Verzeichnisse kann das Beispiel aber auch übersetzt werden, nachdem die Bezüge auf das Unterverzeichnis entfernt wurden.)
lied/lied.dox
/** @file lied/lied.dox
@brief Dokumentation für den Namensraum "lied" */
/** @namespace lied
@brief Lied-Klassen, die jeweils die Operation "ausgeben" unterstützen */lied/kuckuck.h
#ifndef lied_kuckuck_h_INCLUDED_20030123
#define lied_kuckuck_h_INCLUDED_20030123
/** @file lied/kuckuck.h
@brief Das Lied "Auf einem Baum ein Kuckuck sass" */
namespace lied
{ /// Das Lied "Auf einem Baum ein Kuckuck sass"
struct kuckuck
{ public:
/// Das Lied ausgeben
static void ausgeben();
/// Refrain des Liedes ausgeben
static void refrain(); }; }
#endiflied/kuckuck.cpp
#include <iostream>
#include <ostream>
#include "kuckuck.h"
void ::lied::kuckuck::refrain()
{ ::std::cout << "Sim sa la dim, bam ba,\n";
::std::cout << "Sa la du, sa la dim -\n"; }
void ::lied::kuckuck::ausgeben()
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"; refrain();
::std::cout << "Auf einem Baum ein Kuckuck sass.\n"; }
{ ::std::cout << "Da kam ein junger Jaeger, -\n"; refrain();
::std::cout << "Da kam ein junger Jaegersmann.\n"; }
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"; refrain();
::std::cout << "Der schoss den armen Kuckuck tot.\n"; }}lied/vogel.h
#ifndef lied_vogel_h_INCLUDED_20030123
#define lied_vogel_h_INCLUDED_20030123
/** @file lied/vogel.h
@brief Das Lied "Ein Vogel wollte Hochzeit machen" */
namespace lied
{ /// Das Lied "Ein Vogel wollte Hochzeit machen"
struct vogel
{ public:
/// Das Lied ausgeben
static void ausgeben();
/// Refrain des Liedes ausgeben
static void refrain(); }; }
#endiflied/vogel.cpp
#include <iostream>
#include <ostream>
#include "vogel.h"
void ::lied::vogel::refrain()
{ ::std::cout << "Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!\n"; }
void ::lied::vogel::ausgeben()
{ { ::std::cout << "Ein Vogel wollte Hochzeit machen, in dem gruenen Walde.\n";
refrain(); }
{ ::std::cout << "Die Drossel war der Braeutigam, die Amsel war die Braute.\n";
refrain(); }
{ ::std::cout << "Die Lerche, die Lerche, die fuehrt die Braut zur Kerche.\n";
refrain(); }}lied/lied.h
#ifndef lied_lied_h_INCLUDED_20030123
#define lied_lied_h_INCLUDED_20030123
/** @file lied/lied.h
@brief Enthaelt die Kopfdateien aller Lieder (also die
Kopfdateien aller Klassen im Namensraum "lied".) */
#include "kuckuck.h"
#include "vogel.h"
#endiflied_ec.cpp
/** @file lied_ec.cpp
@brief Beispielklient fuer die Klassen des Namensraums
"lied". */
#include <iostream>
#include <ostream>
#include "lied/lied.h"
int main()
{ ::lied::kuckuck::ausgeben();
::lied::vogel ::ausgeben(); }::std::cout
Auf einem Baum ein Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Auf einem Baum ein Kuckuck sass.
Da kam ein junger Jaeger, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Da kam ein junger Jaegersmann.
Der schoss den armen Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der schoss den armen Kuckuck tot.
Ein Vogel wollte Hochzeit machen, in dem gruenen Walde.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Die Drossel war der Braeutigam, die Amsel war die Braute.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Die Lerche, die Lerche, die fuehrt die Braut zur Kerche.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Es wäre auch möglich, die Funktionsdefinitionen in den Implementationsdateien in eine Namensraumdefinition zu schreiben, die mit "namspace lied {" beginnt und dann auf die vorangestellte Eingrenzung "::lied::" zu verzichten. Wenn dann aber ein Funktionsname falsch geschrieben werden würde, so würde er so zu dem Namensraum hinzugefügt werden. Ohne solch ein Klassendefinition ist dies nicht möglich, so daß dann solch ein Fehler aufgrund einer Fehlermeldung der C++ -Implementation schneller bemerkt werden würde. Deswegen wird die Schreibweise mit den eingegrenzten Namen hier bevorzugt.
- / Übersetzen
- Erstellen Sie die obigen Quelldateien und starten Sie den Beispielklienten.
Doxygen -Dokumentation für Namensräume
Wenn bei der Verarbeitung durch Doxygen die Variable "EXTRACT_ALL" den Wert "NO" hat, dann müssen Dateien (mit "@file"), Klassen und Namensräume dokumentiert sein, damit ihre jeweiligen Einträge (Inhalte) dokumentiert werden. Daher empfiehlt es sich neben den Dateien und Klassen auch die Namensräume immer zu dokumentieren.
- Die Doxygen -Dokumentation für einen Namensraum kann an folgenden Stellen stehen:
- vor irgendeiner der Namensraumdefinitionen,
- in einem separatem Kommentar mit einem Kommando "@namespace", der auch in einer eigenen Datei enthalten sein kann.
Hier wurde die zweite Möglichkeit gewählt und zur Dokumentation des Namensraums eine eigene Datei "lied/lied.dox" gewählt. Allerdings wäre die Datei "lied/lied.h" ebenfalls ein ausgezeichneter Ort, um diese Dokumentation aufzunehmen. Hier wurde nur deshalb eine extra Datei verwendet, um daran zu erklären, wie dies zu geschehen hat.
Damit Doxygen alle hier vorkommenden Dateien verarbeitet, ist in der Konfigurationsdatei (normalerweise die Datei "Doxyfile") die Festlegung "FILE_PATTERNS = *.cpp *.h *.dox" vorzunehmen.
Die Dateien des obigen Liedprojekts sollen in einem Unterverzeichnis "lied" enthalten sein. Damit Doxygen Dateien in solchen Unterverzeichnisse (relativ zu dem Verzeichnis, in dem Doxygen gestartet wird) verarbeitet, ist in der Konfigurationsdatei die Festlegung "RECURSIVE = YES" vorzunehmen.
Allerdings gibt es noch eine Beobachtung, wenn Doxygen die hier aufgelisteten Dateien verarbeiten soll: Das Programm scheint die Schreibweise "::lied::kuckuck::refrain" in der Datei "lied/kuckuck.cpp" nicht der in der Datei "lied/kuckuck.h" deklarierten Funktion "refrain" zuordnen zu wollen und erzeugt die Warnungsmeldung "lied/kuckuck.cpp:5: Warning: no matching class member found for void::lied::kuckuck::refrain()". Dieses Problem kann dadurch umgangen werden, daß der Quelltext "::lied::kuckuck::refrain" in der Datei "lied/kuckuck.cpp" unter Fortlassung des ersten Bereichsauflösungsoperators "::" durch den Quelltext "lied::kuckuck::refrain" ersetzt wird und entsprechende Änderungen auch an anderen Stellen vorgenommen werden.
Außerdem setzt Doxygen derzeit anscheinend voraus, daß die Quelldateien mit der Kodierung "ISO-8859-1" zu interpretieren sind. Wurde statt dessen die Kodierung "UTF-8" verwendet, so werden die Daten in vielen Fällen von Doxygen zwar trotzdem verarbeitet, aber die erzeugten HTML -Dateien enthalten dann Umlaute oder andere Sonderzeichen der Kodierung "UTF-8" aber gleichzeitig die dann unzutreffende Kennzeichnung "charset=iso-8859-1". Dieses Problem kann dadurch umgangen werden, daß diese Kennzeichnung in den HTML -Dateien dann nachträglich in die Kennzeichnung "charset=UTF-8" umgeschrieben wird.
- / Dokumentation erzeugen
- Verwenden Sie Doxygen, um die Dokumentation als Menge von HTML -Seiten zu erzeugen und prüfen Sie, ob alle Angaben aus den Dokumentationskommentaren dort zu finden sind.
Verschachtelte Namensräume
Recht verbreitet ist es, alle Bezeichner eines Projektes in einem Namensraum zu definieren. Wenn eine Liedersammlung nur Bestandteil eines Projektes ist, das noch andere „Bücher“ (Gebiete, Themen) enthält, dann kann ein übergreifender Projekt-Namensraum verwendet werden, der alle Gebiete umfaßt.
In diesem Lehrtext wird in solchen Fällen der Namensraum "tut" (engl. “tutorial ”, Lehrtext) verwendet. Er soll in den Fällen, in denen Namensräume definiert werden, der Namensraum für die Definitionen dieses Lehrtextes sein. Ein Bestandteil der Definitionen dieses Lehrtextes sind die Lieder. Daher kann der Namensraum "lied" im Namensraum "tut" definiert werden und die Klasse "kuckuck" wird mit dem id-Ausdruck "::tut::lied::kuckuck" bezeichnet. Es handelt sich also um die Klasse "kuckuck", die im Namensraum "lied" enthalten ist, der im Namensraum "tut" liegt, da „Auf einem Baum ein Kuckuck saß“ (Klasse "kuckuck") ein Lied (Namensraum "lied") ist, welches zu diesem Lehrtext (Namensraum "tut") gehört.
Das folgende Beispiel zeigt, wie der Namensraum "lied" innerhalb des Namensraums "tut" definiert wird.
Die Angaben zur Erzeugung der Doxygen -Dokumentation für den Namensraum befindet sich diesmal nicht in einer extra Datei, sondern in der Datei "tut/lied/lied.h".
tut/lied/kuckuck.h
#ifndef tut_lied_kuckuck_h_INCLUDED_20030123
#define tut_lied_kuckuck_h_INCLUDED_20030123
/** @file tut/lied/kuckuck.h
@brief Das Lied "Auf einem Baum ein Kuckuck sass" */
namespace tut
{ namespace lied
{ /// Das Lied "Auf einem Baum ein Kuckuck sass"
struct kuckuck
{ public:
/// Das Lied ausgeben
static void ausgeben();
/// Refrain des Liedes ausgeben
static void refrain(); }; }}
#endiftut/lied/kuckuck.cpp
#include <iostream>
#include <ostream>
#include "kuckuck.h"
void ::tut::lied::kuckuck::refrain()
{ ::std::cout << "Sim sa la dim, bam ba,\n";
::std::cout << "Sa la du, sa la dim -\n"; }
void ::tut::lied::kuckuck::ausgeben()
{ { ::std::cout << "Auf einem Baum ein Kuckuck, -\n"; refrain();
::std::cout << "Auf einem Baum ein Kuckuck sass.\n"; }
{ ::std::cout << "Da kam ein junger Jaeger, -\n"; refrain();
::std::cout << "Da kam ein junger Jaegersmann.\n"; }
{ ::std::cout << "Der schoss den armen Kuckuck, -\n"; refrain();
::std::cout << "Der schoss den armen Kuckuck tot.\n"; }}tut/lied/vogel.h
#ifndef tut_lied_vogel_h_INCLUDED_20030123
#define tut_lied_vogel_h_INCLUDED_20030123
/** @file tut/lied/vogel.h
@brief Das Lied "Ein Vogel wollte Hochzeit machen" */
namespace tut
{ namespace lied
{ /// Das Lied "Ein Vogel wollte Hochzeit machen"
struct vogel
{ public:
/// Das Lied ausgeben
static void ausgeben();
/// Refrain des Liedes ausgeben
static void refrain(); }; }}
#endiftut/lied/vogel.cpp
#include <iostream>
#include <ostream>
#include "vogel.h"
void ::tut::lied::vogel::refrain()
{ ::std::cout << "Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!\n"; }
void ::tut::lied::vogel::ausgeben()
{ { ::std::cout << "Ein Vogel wollte Hochzeit machen, in dem gruenen Walde.\n";
refrain(); }
{ ::std::cout << "Die Drossel war der Braeutigam, die Amsel war die Braute.\n";
refrain(); }
{ ::std::cout << "Die Lerche, die Lerche, die fuehrt die Braut zur Kerche.\n";
refrain(); }}tut/lied/lied.h
#ifndef tut_lied_lied_h_INCLUDED_20030123
#define tut_lied_lied_h_INCLUDED_20030123
/** @file tut/lied/lied.h
@brief Enthaelt die Kopfdateien aller Lieder (also die
Kopfdateien aller Klassen im Namensraum "lied".) */
/** @namespace ::tut::lied
@brief Lied-Klassen, die jeweils die Operation "ausgeben" unterstützen */
#include "kuckuck.h"
#include "vogel.h"
#endiflied_ec.cpp
/** @file lied_ec.cpp
@brief Beispielklient fuer die Klassen des Namensraums
"lied". */
#include <iostream>
#include <ostream>
#include "tut/lied/lied.h"
int main()
{ ::tut::lied::kuckuck::ausgeben();
::tut::lied::vogel ::ausgeben(); }::std::cout
Auf einem Baum ein Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Auf einem Baum ein Kuckuck sass.
Da kam ein junger Jaeger, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Da kam ein junger Jaegersmann.
Der schoss den armen Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der schoss den armen Kuckuck tot.
Ein Vogel wollte Hochzeit machen, in dem gruenen Walde.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Die Drossel war der Braeutigam, die Amsel war die Braute.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!
Die Lerche, die Lerche, die fuehrt die Braut zur Kerche.
Fi-di-ra-la-la, fi-di-ra-la-la, fi-di-ra-la-la-la-la!- / Übersetzen
- Erstellen Sie die obigen Quelldateien und starten Sie den Beispielklienten.
- / Dokumentation erzeugen
- Verwenden Sie Doxygen, um die Dokumentation als Menge von HTML -Seiten zu erzeugen und prüfen Sie, ob alle Angaben aus den Dokumentationskommentaren dort zu finden sind.
Warum der ID-Ausdruck "::std"?
Die Möglichkeit verschachtelter Namensräume ist auch der Grund dafür, den ID-Ausdruck "::std" statt des ID-Ausdrucks "std" zu verwenden. Der erste bezeichnet den Namensraum "::std" (im globalen Namensraum), während "std" je nach dem Ort der Verwendung auch einen anderen (inneren) Namensraum bezeichnen könnte. Das folgende Beispiel zeigt, wie dies zu Störungen führen kann.
stdredef.cpp
#include <iostream>
#include <ostream>
namespace namensraum
{ namespace std {}
void funktion()
{ std::cout << "Hallo\n"; }}
int main()
{ namensraum::funktion(); }Konsole
"stdredef.cpp", line 6: error: namespace "namensraum::std" has no member "cout"
{ std::cout << "Hallo\n"; }
^
1 error detected in the compilation of "stdredef.cpp".
Im Namensraum "namensraum" bezeichnet der Name "std" nicht den globalen Namensraum "::std", sondern einen lokalen Namensraum, in dem es keinen Namen "cout" gibt. Daher wird "std::cout" nicht gefunden. Die Verwendung von "::std::cout" behebt das Problem jedoch.
Konstanten-Klasse in einem Namensraum
Auch eine Klasse mit Wertfunktionen, beispielsweise für mathematische Konstanten, kann in sinnvoller Weise in einen Namensraum für mathematische Klassen gelegt werden, etwa um sie von einer anderen Klassen "constant" für physikalische Konstanten zu unterscheiden.
Das Modul "constant" braucht keine Implementationsdatei "constant.cpp", obwohl eine leere Implementationsdatei mit einem erklärenden Kommentar als Platzhalter verwendet werden könnte, um zu dokumentieren, daß ihr Fehlen kein Fehler ist.
tut/math/constant.h
#ifndef tut_math_constant_h_INCLUDED_20030615
#define tut_math_constant_h_INCLUDED_20030615
/** @file constant.h
@brief Einige mathematische Konstanten. */
namespace tut
{ namespace math
{ /// Einige mathematische Konstanten
class constant
{ public:
static double pi();
static double e(); }; }}
inline double ::tut::math::constant::pi()
{ return 3.1415926535897932384626433832795028841972; }
inline double ::tut::math::constant::e()
{ return 2.7182818284590452353602874713526624977572; }
#endifconstant_ec.cpp
/** @file constant_ec.cpp
@brief Bespielklient (ec = example client) fuer
die Klasse Modul "tut::math::constant". */
#include <iostream>
#include <ostream>
#include "tut/math/constant.h"
int main()
{ ::std::cout << "Der Umfang eines Kreises mit dem Radius 9.8 ist " <<
2 * ::tut::math::constant::pi() * 9.8 << ".\n"; }::std::cout
Der Umfang eines Kreises mit dem Radius 9.8 ist 61.5752.
Anonyme Namensräume
Wenn ein Namensraum verwendet werden soll, um Bezeichner einer Übersetzungseinheit für andere Übersetzungseinheiten unsichtbar zu machen, könnte ein „geheimer“ Namensraumname verwendet werden, der anderen Übersetzungseinheiten nicht bekannt ist. Statt dessen ist es auch möglich, den Bezeichner einer Namensraumdefinition wegzulassen. Dann wird praktisch ein für die Übersetzungseinheit festgelegter einmaliger interner Name generiert. (Bei wiederholten namenlosen Namensraumdefinitionen in derselben Übersetzungseinheit wird immer der gleiche Namen erzeugt.) Dadurch sind solche Bezeichner praktisch aus anderen Übersetzungseinheiten nicht erreichbar, obwohl sie es sonst wären, wenn ihr Name in einer anderen Übersetzungseinheit richtig erraten worden wäre.
Darüber hinaus wird implizit eine "using"-Direktive für die Übersetzungseinheit mit der anonymen Namensraumdefinition erzeugt, die es erlaubt, in dieser Übersetzungseinheit ohne weiteres auf die Bezeichner des Namensraums zuzugreifen. Die Wirkung dieses Vorgehens ist dann insgesamt, daß die Bezeichner dieser Namensraumdefinitionen innerhalb der sie enthaltenden Übersetzungseinheit verwendet werden können, aber für andere Übersetzungseinheiten unerreichbar sind.