vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 17



Namensbereiche

Namensbereiche sind neu in ANSI C++. Sie helfen den Programmierern, Namenskonflikte zu vermeiden, wenn mehr als eine Bibliothek verwendet wird. Heute lernen Sie,

Zum Einstieg

Namenskonflikte sind schon seit jeher für C- und C++-Programmierer eine Quelle des Ärgers. Durch die Einführung der Namensbereiche bietet der ANSI-Standard nun eine Möglichkeit, diese Probleme zu umgehen, doch ist zu beachten, daß Namensbereiche noch nicht von allen Compilern unterstützt werden.

Ein Namenskonflikt liegt vor, wenn ein Name in zwei Teilen Ihres Programms im gleichen Gültigkeitsbereich auftaucht. Am häufigsten tritt dieser Fall auf, wenn mehrere Bibliotheken verwendet werden. So wird zum Beispiel eine Bibliothek von Container- Klassen mit größter Wahrscheinlichkeit eine List-Klasse deklarieren und implementieren. (Näheres zu Container-Klassen in Kapitel 19, »Templates«).

Es ist ebenfalls keine Überraschung, eine List-Klasse in einer Bibliothek für grafische Fensteroberflächen zu finden. Angenommen, Sie wollen in ihrer Anwendung eine Reihe von Fenster verwalten. Und weiterhin angenommen, Sie verwenden dazu die List-Klasse aus der Container-Klassen-Bibliothek. Sie deklarieren eine Instanz der List-Klasse aus der Fenster-Bibliothek, um Ihre Fenster aufzunehmen. Überrascht stellen Sie fest, daß die Elementfunktionen, die Sie aufrufen wollen, nicht verfügbar sind. Der Compiler hat Ihre List-Deklaration mit dem List-Container in der Standard-Bibliothek abgeglichen, dabei wollten Sie eigentlich die List-Klasse aus der Fenster-Bibliothek Ihres Drittanbieters verwenden

Namensbereiche werden dazu benutzt, um globale Namensbereiche aufzuteilen und Namenskonflikte zu eliminieren oder zumindest zu reduzieren. Namensbereiche sind in etlicher Hinsicht den Klassen ähnlich, vor allem in der Syntax.

Elemente, die in einem Namensbereich deklariert wurden, sind »Besitz« des Namensbereichs. Alle Elemente innerhalb eines Namensbereichs sind öffentlich sichtbar. Namensbereiche können ineinander verschachtelt werden. Funktionen können innerhalb und außerhalb des Rumpfs eines Namensbereichs definiert werden. Wird die Funktion außerhalb definiert, muß der Name des Namensbereichs explizit angegeben werden.

Funktionen und Klassen werden über den Namen aufgelöst

Während der Compiler den Quellcode parst und eine Liste der Funktions- und Variablennamen erstellt, versucht er, eventuelle Namenskonflikte aufzudecken.

Der Compiler kann jedoch keine Namenskonflikte aufdecken, die über die Grenzen von Übersetzungseinheiten (zum Beispiel Objektdateien) hinausgehen. Dazu ist der Linker da. Aus diesem Grunde gibt der Compiler auch keine Fehlermeldung aus.

Es kommt nicht selten vor, daß der Linker Sie mit der Fehlermeldung Identifier multiply defined darauf aufmerksam macht, daß etwas mehrfach definiert wurde (Identifier ist irgendein benannter Typ). Sie erhalten diese Linker-Meldung, wenn Sie den gleichen Namen, man spricht auch von Bezeichnern, im gleichen Gültigkeitsbereich in verschiedenen Übersetzungseinheiten definiert haben. Sie erhalten einen Compiler- Fehler, wenn Sie einen Namen innerhalb einer einzigen Datei im gleichen Gültigkeitsbereich neu definieren. Das folgende Beispiel wird nach dem Kompilieren und Linken eine Fehlermeldung des Linkers verursachen:

// Datei first.cpp
int integerWert = 0 ;
int main( ) {
int integerWert = 0 ;
// . . .
return 0 ;
} ;

// Datei second.cpp
int integerWert = 0 ;
// Ende von second.cpp

Mein Linker hat mir für obiges Programm folgende Diagnose gestellt: in second.obj integerWert already defined in first.obj. Wären diese Namen in unterschiedlichen Gültigkeitsbereichen definiert, würden sich weder Compiler noch Linker beschweren.

Es ist auch möglich, daß Sie eine Warnung vom Compiler erhalten, die besagt, daß ein Name verdeckt wird. Der Compiler weist Sie in diesem Falle darauf hin, daß die Variable integerWert in der main()-Funktion von first.cpp die globale Variable gleichen Namens versteckt.

Um die außerhalb von main() deklarierte integerWert-Variable verwenden zu können, müssen Sie explizit den Gültigkeitsbereich der Variablen angeben. Betrachten Sie folgendes Beispiel, in welchem dem integerWert außerhalb (nicht innerhalb) von main() der Wert 10 zugewiesen wird:

// Datei first.cpp
int integerWert = 0 ;
int main( )
{
int integerWert = 0 ;
::integerWert = 10 ; // der globalen Variable zuweisen
// . . .
return 0 ;
} ;

// Datei second.cpp
int integerWert = 0 ;
// Ende von second.cpp

Beachten Sie den Einsatz des Gültigkeitsbereichauflösungsoperators ::, der Ihnen zeigt, daß die betreffende Variable integerWert global und nicht lokal ist.

Das Problem mit den beiden globalen Integer-Variablen, die außerhalb der Funktionen definiert sind, ist, daß sie den gleichen Namen und die gleiche Sichtbarkeit haben und deshalb einen Linker-Fehler auslösen.

Der Begriff Sichtbarkeit bezeichnet den Gültigkeitsbereich eines definierten Objekts, sei es eine Variable, eine Klasse oder eine Funktion. So ist zum Beispiel der Gültigkeitsbereich einer Variablen, die außerhalb aller Funktionen definiert und deklariert wurde, global, das heißt, er erstreckt sich über die ganze Datei. Die Sichtbarkeit der Variablen beginnt mit ihrer Definition und reicht bis zum Ende der Datei. Eine Variable mit lokalem Gültigkeitsbereich ist in einem Block definiert. Am häufigsten sind diese Variablen innerhalb von Funktionen zu finden. Das folgende Beispiel verwendet Variablen in verschiedenen Gültigkeitsbereichen:

int globalerInt  = 5 ;
void f( )
{
int lokalerInt = 10 ;
}
int main( )
{
int lokalerInt = 15 ;
{
int auchLokal = 20 ;
int lokalerInt = 30 ;
}
return 0 ;
}

Die erste int-Variable, globalerInt, ist innerhalb der Funktionen f() und in main() sichtbar. Die nächste Definition, lokalerInt, steht in der Funktion f(). Diese Variable hat lokalen Gültigkeitsbereich, das heißt, sie ist nur in dem Block sichtbar, in dem sie definiert wurde.

main() kann nicht auf die Variable lokalerInt aus der Funktion f() zugreifen. Wenn die Funktion zurückkehrt, verliert lokalerInt ihren Gültigkeitsbereich. Die dritte Definition von lokalerInt befindet sich in main(). Ihr Gültigkeitsbereich ist ebenfalls lokal.

Beachten Sie, daß es nicht zu Konflikten zwischen lokalerInt von main() und lokalerInt von f() kommt! Die nächsten zwei Definitionen, auchLokal und lokalerInt sind beide lokal gültig. Sobald die schließende Klammer erreicht ist, sind diese zwei Variablen nicht mehr sichtbar.

Ich möchte Sie darauf aufmerksam machen, daß diese lokalerInt im innersten Block die lokalerInt-Variable verdeckt, die vor der öffnenden Klammer definiert wurde (also die zweite lokalerInt-Variable in diesem Programm). Hinter der schließenden Klammer wird die zweite lokalerInt-Variable wieder sichtbar. Alle Änderungen, die sich auf die innerhalb der Klammern definierte lokalerInt-Variable beziehen, haben keine Auswirkungen auf den Inhalt der äußeren Variablen mit gleichem Namen.

Namen können externe und interne Bindung aufweisen. Diese zwei Begriffe beziehen sich auf die Verwendung oder die Verfügbarkeit eines Namens innerhalb eines einzigen oder über mehrere Übersetzungseinheiten hinweg. So kann zum Beispiel eine Variable, die mit interner Bindung definiert wurde, nur von den Funktionen innerhalb ihrer Übersetzungseinheit genutzt werden. Namen mit externer Bindung stehen auch anderen Übersetzungseinheiten zur Verfügung. Das folgende Beispiel veranschaulicht beide Formen der Bindung:

// Datei: first.cpp
int externerInt = 5 ;
const int j = 10 ;
int main()
{
return 0 ;
}

// Datei: second.cpp
extern int externerInt ;
int einExternerInt = 10 ;
const int j = 10 ;

Die in first.cpp definierte Variable externerInt hat externe Bindung. Auch wenn sie in first.cpp definiert wurde, kann second.cpp darauf zugreifen. Die beiden Variablen j, die in beiden Dateien als const definiert wurden, haben standardmäßig interne Bindung. Sie können die const-Vorgabe überschreiben, indem Sie wie folgt explizit eine externe Bindung deklarieren:

// Datei: first.cpp
extern const int j = 10 ;

// Datei: second.cpp
extern const int j ;
#include <iostream>
int main()
{
std::cout << "j ist " << j << std::endl ;
return 0 ;
}

Beachten Sie, daß der Aufruf von cout über die Namensbereichangabe std erfolgt. Auf diese Weise kann man auf alle »Standard«-Objekte in der ANSI-Standard-Bibliothek zugreifen. Wird dieses Code-Beispiel ausgeführt, produziert es folgendes Ergebnis:

j ist 10

Das Standardisierungskomitee hat folgende Schreibweise für veraltet erklärt:

static int staticInt = 10 ;
int main()
{
//...
}

Von der Verwendung von static zur Einschränkung des Gültigkeitsbereichs externer Variablen wird abgeraten. Sie ist wahrscheinlich bald nicht mehr zulässig. Sie sollten statt dessen lieber auf Namensbereiche zurückgreifen.

Was Sie tun sollten

... und was nicht

Verwenden Sie möglichst nicht das Schlüsselwort static für Variablen mit globalen Gültigkeitsbereich.

Verwenden Sie statt dessen Namensbereiche.

Namensbereiche einrichten

Die Syntax einer Namensbereichdeklaration ähnelt sehr der Syntax einer Struktur- oder Klassendeklaration. Zuerst kommt das Schlüsselwort namespace gefolgt von einem optionalen Bezeichner für den Namensbereich und einer öffnenden geschweiften Klammer. Beendet wird der Namensbereich mit einer schließenden geschweiften Klammer, jedoch ohne Semikolon.

Beispiel:

namespace Window 
{
void move( int x, int y) ;
}

Der Bezeichner Window dient der eindeutigen Identifizierung des Namensbereichs. Einen einmal eingerichteten Namensbereich können Sie mehrfach verwenden. Dabei ist es egal, ob dies innerhalb einer Datei oder über mehrere Übersetzungseinheiten hinweg geschieht. Der Namensbereich std der C++-Standardbibliothek ist vielleicht das beste Beispiel dafür. Daß die Elemente der Standardbibliothek in einem Namensbereich zusammengefaßt sind, ist insofern sinnvoll, als die Standardbibliothek eine logische Zusammenstellung von Funktionalität darstellt.

Das den Namensbereichen zugrundeliegende Konzept besteht darin, zusammengehörige Elemente in einem spezifizierten (bezeichneten) Bereich zusammenzufassen. Hier ein kleines Beispiel für einen Namensbereich, der sich über mehrere Header-Dateien erstreckt:

// header1.h
namespace Window
{
void move( int x, int y) ;
}

// header2.h
namespace Window
{
void resize( int x, int y ) ;
}

Typen deklarieren und definieren

Sie können innerhalb von Namensbereichen Typen und Funktionen sowohl deklarieren als auch definieren. Das ist natürlich eine Frage des Entwurfs. Für einen guten Entwurf empfiehlt es sich, Schnittstelle und Implementierung zu trennen. Diesem Prinzip sollten Sie nicht nur bei Klassen, sondern auch bei den Namensbereichen folgen. Als Negativ-Beispiel möchte ich Ihnen einen schlecht konzipierten, unübersichtlichen Namensbereich vorstellen:

namespace Window {
// . . . sonstige Deklarationen und Variablendefinitionen
void move( int x, int y) ; // Deklarationen
void resize( int x, int y ) ;
// . . . weitere Deklarationen und Variablendefinitionen

void move( int x, int y )
{
if( x < MAX_SCREEN_X && x > 0 )
if( y < MAX_SCREEN_Y && y > 0 )
platform.move( x, y ) ; // spezielle Routine
}

void resize( int x, int y )
{
if( x < MAX_SIZE_X && x > 0 )
if( y < MAX_SIZE_Y && y > 0 )
platform.resize( x, y ) ; // spezielle Routine
}
// . . . weitere Definitionen
}

Hier können Sie sehen, wie schnell ein Namensbereich unübersichtlich werden kann. Dabei ist das obige Beispiel nur 20 Zeilen lang. Vielleicht können Sie sich vorstellen, wie es aussehen würde, wenn der Namensbereich viermal so lang wäre.

Funktionen außerhalb von Namensbereichen definieren

Sie sollten Namensbereichfunktionen außerhalb des Namensbereichrumpfs definieren. Damit erreichen Sie eine klare Trennung zwischen Deklaration einer Funktion und ihrer Definition - und halten Ihren Namensbereich übersichtlich. Indem Sie die Funktionsdefinition von dem Namensbereich trennen, können Sie den Namensbereich und die darin enthaltenen Deklarationen in eine Header-Datei aufnehmen und die Definitionen in einer Implementierungsdatei unterbringen.

Beispiel:

// Datei header.h
namespace Window {
void move( int x, int y) ;
// weitere Deklarationen
}

// Datei impl.cpp
void Window::move( int x, int y )
{
// Code zum Verschieben des Fensters
}

Neue Elemente aufnehmen

Um neue Elemente in einen Namensbereich aufzunehmen, muß man sie im Rumpf des Namensbereichs aufführen. Sie können keine neuen Elemente mit Hilfe der Qualifizierer-Syntax (NameDesNamensbereichs::NameDesElements) aufnehmen. Von dieser Art der Definition können sie höchstens eine Warnung vom Compiler erwarten. Das folgende Beispiel zeigt diesen Fehler:

namespace Window {
// viele Deklarationen
}
//einiger Code
int Window::newIntegerInNamespace ; // das geht leider nicht

Der obige Code ist nicht zulässig. Ihr Compiler wird eine Fehlerbeschreibung ausgeben. Um den Fehler zu korrigieren - oder ihn gänzlich zu vermeiden - müssen Sie die Deklaration in den Rumpf des Namensbereichs verschieben.

Alle Elemente innerhalb eines Namensbereichs sind öffentlich. Der folgende Code läßt sich demzufolge nicht kompilieren.

namespace Window {
private:
void move( int x, int y ) ;
}

Namensbereiche verschachteln

Namensbereiche lassen sich verschachteln. Und daß man sie verschachteln kann, liegt daran, daß die Definition eines Namensbereichs auch gleichzeitig eine Deklaration ist. Bei verschachtelten Namensbereichen müssen Sie jeden Namensbereich durch einen eigenen eindeutigen Namen qualifizieren. Im folgenden Beispiel sehen Sie einen bezeichneten Namensbereich innerhalb eines anderen bezeichneten Namensbereichs:

namespace Window {
namespace Pane {
void size( int x, int y ) ;
}
}

Um auf die Funktion size() von außerhalb des Namensbereichs Window zuzugreifen, müssen Sie die Funktion mit beiden der sie umschließenden Namensbereichen qualifizieren. Die Qualifizierung würde damit wie folgt aussehen:

int main( )
{
Window::Pane::size( 10, 20 ) ;
return 0 ;
}

Namensbereiche einsetzen

Betrachten wir ein Beispiel für den Einsatz eines Namensbereichs und den dazugehörigen Gültigkeitsbereichsauflösungsoperator. Zuerst werde ich alle im Beispiel zum Einsatz kommenden Typen und Funktionen im Namensbereich Window deklarieren. Nachdem ich alles Notwendige definiert habe, definiere ich die deklarierten Elementfunktionen. Diese Elementfunktionen werden außerhalb des Namensbereichs definiert. Die Namen werden explizit mit Hilfe des Gültigkeitsbereichsauflösungsoperators identifiziert. Listing 17.1 zeigt, wie man Namensbereiche verwendet.

Listing 17.1: Verwendung eines Namensbereichs

 1: #include <iostream>
2:
3: namespace Window
4: {
5: const int MAX_X = 30 ;
6: const int MAX_Y = 40 ;
7: class Pane
8: {
9: public:
10: Pane() ;
11: ~Pane() ;
12: void size( int x, int y ) ;
13: void move( int x, int y ) ;
14: void show( ) ;
15: private:
16: static int cnt ;
17: int x ;
18: int y ;
19: };
20: }
21:
22: int Window::Pane::cnt = 0 ;
23: Window::Pane::Pane() : x(0), y(0) { }
24: Window::Pane::~Pane() { }
25:
26: void Window::Pane::size( int x, int y )
27: {
28: if( x < Window::MAX_X && x > 0 )
29: Pane::x = x ;
30: if( y < Window::MAX_Y && y > 0 )
31: Pane::y = y ;
32: }
33: void Window::Pane::move( int x, int y )
34: {
35: if( x < Window::MAX_X && x > 0 )
36: Pane::x = x ;
37: if( y < Window::MAX_Y && y > 0 )
38: Pane::y = y ;
39: }
40: void Window::Pane::show( )
41: {
42: std::cout << "x " << Pane::x ;
43: std::cout << " y " << Pane::y << std::endl ;
44: }
45:
46: int main( )
47: {
48: Window::Pane pane ;
49:
50: pane.move( 20, 20 ) ;
51: pane.show( ) ;
52:
53: return 0 ;
54: }

x 20 y 20

Beachten Sie, daß die Klasse Pane innerhalb des Namensbereichs Window deklariert ist. Deshalb müssen Sie bei Zugriffen auf Pane den Qualifizierer Window:: verwenden.

Die statische Variable cnt, die in Pane in Zeile 16 deklariert wird, wird wie gewohnt definiert. Beachten Sie auch, daß die Konstanten MAX_X und MAX_Y in der Funktion Pane::size() (Zeilen 26 bis 32) vollständig qualifiziert werden. Das liegt daran, daß der aktuelle Gültigkeitsbereich in der Funktion der Gültigkeitsbereich von Pane ist. Würden Sie auf die Qualifizierung verzichten, würde der Compiler eine Fehlermeldung ausgeben. Gleiches gilt für die Funktion Pane::move().

Interessant ist auch die Qualifizierung von Pane::x und Pane::y in den beiden Funktionsdefinitionen. Warum, werden Sie sich fragen? Würden Sie die Funktion Pane::move() folgendermaßen aufsetzen, hätten Sie ein Problem:

void Window::Pane::move( int x, int y )
{
if( x < Window::MAX_X && x > 0 )
x = x ;
if( y < Window::MAX_Y && y > 0 )
y = y ;
Platform::move( x, y ) ;
}

Sehen Sie das Problem? Die Fehlermeldung Ihres Compilers wird mit Sicherheit nicht sehr aufschlußreich sein, falls Sie überhaupt eine Meldung erhalten.

Das Problem liegt in den Argumenten der Funktion. Die Argumente x und y verdekken die privaten Instanzvariablen x und y, die in der Klasse Pane deklariert wurden. Demzufolge weisen die Anweisungen x und y sich selbst zu:

x = x ;
y = y ;

Das Schlüsselwort using

Das Schlüsselwort using wird für die using-Direktive und die using-Deklaration verwendet. Die Syntax des using-Aufrufs legt dabei fest, ob es sich um die Direktive oder die Deklaration handelt.

Die using-Direktive

Die using-Direktive stellt alle Namen, die in einem Namensbereich deklariert wurden, in den aktuellen Gültigkeitsbereich. Sie können auf die Namen Bezug nehmen, ohne sie mit ihrem entsprechenden Namensbereichsnamen qualifizieren zu müssen. Das folgende Beispiel verdeutlicht den Einsatz der using-Direktive:

namespace Window {
int wert1 = 20 ;
int wert2 = 40 ;
}
. . .
Window::wert1 = 10 ;

using namespace Window ;
wert2 = 30 ;

Der Gültigkeitsbereich der using-Direktive beginnt mit ihrer Deklaration und erstreckt sich bis zum Ende des aktuellen Gültigkeitsbereichs. Beachten Sie, daß wert1 qualifiziert werden muß, während für die Variable wert2 keine Qualifizierung mehr benötigt wird, da die using-Direktive zuvor alle Namen des Namensbereichs in den aktuellen Namensbereich eingeführt hat.

Die using-Direktive kann auf jeder Gültigkeitsebene verwendet werden. Das heißt, Sie können die Direktive auch innerhalb eines Blocks verwenden. Wenn der Block seine Gültigkeit verliert, gilt das auch für alle Namen innerhalb des Namensbereichs. Sehen Sie dazu ein Beispiel:

namespace Window {
int wert1 = 20 ;
int wert2 = 40 ;
}
//. . .
void f()
{
{
using namespace Window ;
wert2 = 30 ;
}
wert2 = 20 ; // Fehler!
}

Die letzte Codezeile in f(), wert2 = 20 ; ist ein Fehler, da wert2 nicht definiert ist. Im darübergelegenen Block ist der Zugriff auf den Namen möglich, weil die Direktive den Namen in den Block einführt. Verliert der Block seinen Gültigkeit, gilt das auch für die Namen im Namensbereich Window.

Lokal deklarierte Variablennamen verdecken alle gleichnamigen Namen eines Namensbereichs, der im gleichen Gültigkeitsbereich eingebunden wurde. Das Verhalten ist vergleichbar mit lokalen Variablen, die globale Variablen verdecken. Auch wenn Sie den Namensbereich nach der lokalen Variablen einführen, hat die lokale Variable den Vorrang. Sehen Sie dazu folgendes Beispiel:

namespace Window {
int wert1 = 20 ;
int wert2 = 40 ;
}
//. . .
void f()
{
int wert2 = 10 ;
using namespace Window ;
std::cout << wert2 << std::endl ;
}

Die Ausgabe dieser Funktion ist 10 und nicht 40. Damit wird bestätigt, daß wert2 aus dem Namensbereich Window durch die Variable wert2 in f() verdeckt ist. Wenn Sie einen Namen aus einem Namensbereich benötigen, müssen Sie den Namen mit dem Namen des Namensbereichs qualifizieren.

Mehrdeutigkeiten können auftreten, wenn Sie einen Namen verwenden, der sowohl global als auch innerhalb eines Namensbereichs definiert ist. Diese Mehrdeutigkeit tritt nicht automatisch bei Einbindung eines Namensbereichs zutage, sondern erst, wenn der Name benutzt wird. Zur Veranschaulichung betrachten Sie folgendes Codefragment:

namespace Window {
int wert1 = 20 ;
}
//. . .
using namespace Window ;
int wert1 = 10 ;
void f( )
{
wert1 = 10 ;
}

Die Mehrdeutigkeit liegt in der Funktion f() vor. Die Direktive führt Window::wert1 in den globalen Namensbereich ein. Da wert1 aber bereits global definiert ist, führt die Verwendung von wert1 in f() zu einem Fehler. Würden Sie die Codezeile aus f() entfernen, gäbe es keinen Fehler mehr.

Die using-Deklaration

Die using-Deklaration ist der using-Direktiven ähnlich, läßt Ihnen jedoch mehr Kontrolle darüber, welche Namen aus einem Namensbereich eingeführt werden sollen. Genauer gesagt, wird die using-Deklaration verwendet, um nur einen bestimmten Namen (eines Namensbereichs) in den aktuellen Gültigkeitsbereich einzubinden. Danach müssen Sie für den Zugriff auf das spezifizierte Objekt lediglich den Namen angeben. Folgendes Beispiel veranschaulicht die Verwendung der using-Deklaration:

namespace Window {
int wert1 = 20 ;
int wert2 = 40 ;
int wert3 = 60 ;
}
//. . .
using Window::wert2 ; // stellt wert2 in den aktuellen Gueltigkeitsbereich
Window::wert1 = 10 ; // wert1 muss qualifiziert werden
value2 = 30 ;
Window::wert3 = 10 ; // wert3 muss qualifiziert werden

Die using-Deklaration führt den spezifizierten Namen in den aktuellen Gültigkeitsbereich ein. Die Deklaration hat keinen Einfluß auf die anderen Namen innerhalb des Namensbereichs. Im vorherigen Beispiel kann auf wert2 ohne weitere Qualifizierung Bezug genommen werden. wert1 und wert3 hingegen müssen weiter qualifiziert werden. Mit der using-Deklaration können Sie also explizit auswählen, welche Namen eines Namensbereichs Sie in einen Gültigkeitsbereich einführen wollen. Darin unterscheidet sich die Deklaration von der Direktive, die alle Namen eines Namensbereichs auf einmal einführt.

Nachdem ein Name in einen Gültigkeitsbereich eingeführt wurde, ist er bis zum Ende dieses Gültigkeitsbereichs sichtbar. Dies Verhalten entspricht dem aller anderen Deklarationen. Eine using-Deklaration ist im globalen Namensbereich aber auch in allen lokalen Gültigkeitsbereichen möglich.

Es führt zu einem Fehler, wenn man einen Namen in einem lokalen Gültigkeitsbereich definiert, in dem bereits ein gleichlautender Namen eines Namensbereichs deklariert wurde. Der umgekehrte Fall würde ebenfalls einen Fehler zur Folge haben. Sehen Sie dazu folgendes Beispiel:

namespace Window {
int wert1 = 20 ;
int wert2 = 40 ;
}
//. . .
void f()
{
int wert2 = 10 ;
using Window::wert2 ; // Mehrfachdeklaration
std::cout << wert2 << std::endl ;
}

Die zweite Zeile in f() führt zu einem Compiler-Fehler, da der Name wert2 bereits definiert wurde. Der gleiche Fehler würde gemeldet, wenn die using-Deklaration vor der Definition der lokalen Variablen wert2 stünde.

Jeder Name, der mit einer using-Deklaration in einen lokalen Gültigkeitsbereich eingebunden wird, verdeckt alle Bezeichner außerhalb dieses Gültigkeitsbereichs. Als Beispiel betrachten wir folgendes Code-Fragment:

namespace Window {
int wert1 = 20 ;
int wert2 = 40 ;
}
int value2 = 10 ;
//. . .
void f()
{
using Window::wert2 ;
std::cout << wert2 << std::endl ;
}

Die using-Deklaration in f() verbirgt wert2 aus dem globalen Namensbereich.

Wie bereits zuvor erwähnt, haben Sie mit der using-Deklaration eine bessere Kontrolle über die Namen, die Sie aus einem Namensbereich einbinden wollen. Eine using- Direktive führt alle Namen eines Namensbereichs in den aktuellen Gültigkeitsbereich ein. Sie sollten die Deklaration der Direktiven vorziehen, denn die Direktive steht im Gegensatz zum eigentlichen Zweck des Namensbereich-Mechanismus. Eine Deklaration ist bestimmter, da Sie explizit angeben, welcher Name in einen Gültigkeitsbereich eingebunden werden soll. Die using-Deklaration führt auch nicht so schnell zur Verstopfung des globalen Namensbereichs, wie das bei einer using-Direktive der Fall ist (es sei denn, Sie deklarieren alle Namen eines Namensbereichs). Verdeckte Namen, verstopfte globale Namensbereiche und Mehrdeutigkeiten werden alle mit der using- Deklaration auf ein beherrschbares Maß reduziert.

Aliase für Namensbereich

Namensbereich-Aliase bieten Ihnen die Möglichkeit, andere Namen für Ihre benannten Namensbereiche anzugeben. Mit einem Alias schaffen Sie eine Kurzbegriff, mit dem Sie auf den Namensbereich Bezug nehmen können. Das ist besonders dann von Vorteil, wenn die Namen der Namensbereiche sehr lang sind. Der Alias hilft Ihnen dabei, langwierige, sich ständig wiederholende Eingaben zu reduzieren. Hierzu ein Beispiel:

namespace die_Software_Firma {
int wert ;
// . . .
}
die_Software_Firma::wert = 10 ;
. . .
namespace dSF = die_Software_Firma;
dSF::wert = 20 ;

Es besteht allerdings die Gefahr, daß ein Alias mit einem bereits bestehenden Namen übereinstimmt. In einem solchen Falle wird der Compiler den Konflikt auffangen und Ihnen die Gelegenheit geben, den Alias umzubenennen.

Der unbenannte Namensbereich

Ein unbenannter Namensbereich ist ein Namensbereich ohne Bezeichnung. Üblicherweise setzt man unbenannte Namensbereiche ein, um globale Daten vor potentiellen Namenskonflikten zwischen Übersetzungseinheiten zu schützen. Jede Übersetzungseinheit hat ihren eigenen einzigartigen unbenannten Namensbereich. Jeder im unbenannten Namensbereich (einer Übersetzungseinheit) definierte Name kann ohne explizite Qualifizierung angesprochen werden. Im folgenden sehen Sie ein Beispiel für zwei unbenannte Namensbereiche in zwei getrennten Dateien:

// Datei: one.cpp
namespace {
int wert ;
char p( char *p ) ;
//. . .
}

// Datei: two.cpp
namespace {
int wert ;
char p( char *p ) ;
//. . .
}
int main( )
{
char c = p( ptr ) ;
}

In obigem Beispiel stehen die Namen wert und Funktion p in beiden Dateien für jeweils eigenständige, unterscheidbare Elemente. Einen Namen aus einem unbenannten Namensbereich kann man innerhalb seiner Übersetzungseinheit ohne Qualifizierung verwenden (siehe Aufruf der Funktion p()). Diese Verwendung deutet auf eine interne using-Direktive für Objekte aus dem unbenannten Namensbereich hin. Aus diesem Grunde können Sie auf Elemente eines unbenannten Namensbereichs nicht aus anderen Übersetzungseinheiten zugreifen. Das Verhalten eines unbenannten Namensbereichs entspricht dem eines static-Objekts mit externer Bindung. Dazu folgendes Beispiel:

static int wert = 10;

Denken Sie daran, daß von der Verwendung des Schlüsselwortes static vom Standardisierungskomitee abgeraten wird. Um den obigen Code zu ersetzen, gibt es jetzt die Namensbereiche. Sie können sich unbenannte Namensbereiche auch als globale Variablen mit interner Bindung vorstellen.

Der Standardnamensbereich std

Das beste Beispiel für einen Namensbereich finden Sie in der C++-Standardbibliothek. Die Standardbibliothek ist komplett eingebettet in den Namensbereich std. Alle Funktionen, Klassen, Objekte und Templates werden innerhalb des Namensbereichs std deklariert.

Sie werden ohne Zweifel auf einen Code wie den folgenden treffen:

#include <iostream>
using namespace std ;

Zur Erinnerung: Die using-Direktive führt alle Namen aus dem benannten Namensbereich ein. Es ist schlechter Stil, die using-Direktive zusammen mit der Standardbibliothek zu verwenden. Warum? Weil damit der Einsatz eines Namensbereichs seinen Sinn verliert. Der globale Namensbereich wird durch all die Namen im Header »verunreinigt«. Denken Sie daran, daß alle Header-Dateien Namensbereiche verwenden. Wenn Sie also mehrere Standard-Header-Dateien einbinden und die using-Direktive verwenden, steht alles, was in den Headern deklariert wurde, auch im globalen Namensbereich. Ich möchte Sie auch darauf aufmerksam machen, daß fast alle Beispiele dieses Buches diese Regel verletzen. Damit möchte ich Sie nicht dazu anhalten, die Regel zu verletzen, sondern es geschieht nur aus dem Grunde, um die Beispiele kurz zu halten. Statt dessen sollten Sie die using-Deklaration verwenden:

#include <iostream>
using std::cin ;
using std::cout ;
using std::endl ;
int main( )
{
int wert = 0 ;
cout << "Wie viele Eier, sagten Sie, wollten Sie?" << endl ;
cin >> wert ;
cout << wert << " Eier als Spiegelei!" << endl ;
return( 0 ) ;
}

So sähe das obige Programm bei der Ausführung aus:

Wie viele Eier, sagten Sie, wollten Sie?
4
4 Eier als Spiegelei!

Als Alternative könnten Sie die Namen, wie im folgenden Codebeispiel, vollständig qualifizieren:

#include <iostream>
int main( )
{
int wert = 0 ;
std::cout << "Wie viele Eier möchten Sie?" << std::endl ;
std::cin >> wert ;
std::cout << wert << " Eier als Spiegelei!" << std::endl ;
return( 0 ) ;
}

So sähe eine Ausgabe dieses Programms aus:

Wie viele Eier möchten Sie?
4
4 Eier als Spiegelei!

Das ist vielleicht für kürzere Programme eine Lösung. Aber sobald die Programme länger werden, kann das recht lästig werden. Stellen Sie sich einmal vor, Sie müssen jedem Namen, den Sie in der Standardbibliothek finden, std:: voranstellen.

Zusammenfassung

Die Erzeugung eines Namensbereichs ist einer Klassendeklaration sehr ähnlich. Auf einige Unterschiede möchte ich Sie jedoch hinweisen. Zum einen folgt auf die schließende geschweifte Klammer eines Namensbereichs kein Semikolon. Zweitens ist ein Namensbereich erweiterbar, während eine Klasse abgeschlossen ist. Sie können die Definition eines Namensbereichs in einer anderen Datei oder in anderen Abschnitten der gleichen Datei erweitern.

Alles, was deklariert werden kann, kann auch in einen Namensbereich gestellt werden. Wenn Sie Klassen für eine wiederverwendbare Bibliothek entwerfen, sollten Sie mit Namensbereichen arbeiten. Funktionen, die innerhalb eines Namensbereichs deklariert werden, sollten außerhalb des Rumpfes des Namensbereichs definiert werden. Damit unterstützen Sie die Trennung von Schnittstelle und Implementierung und verhindern, daß der Namensbereich unübersichtlich wird.

Namensbereiche können verschachtelt werden. Namensbereiche sind Deklarationen und lassen sich demzufolge auch verschachteln. Vergessen Sie nicht, daß Sie Namen verschachtelter Namensbereiche vollständig qualifizieren müssen.

Die using-Direktive dient dazu, alle Namen eines Namensbereichs in den aktuellen Gültigkeitsbereich aufzunehmen. Dadurch wird der globale Namensbereich allerdings oftmals mit nicht benötigten Namen aus dem benannten Namensbereich überschwemmt. Allgemein wird es als schlechter Stil betrachtet, die using-Direktive zu verwenden, besonders im Zusammenhang mit der Standardbibliothek. Verwenden Sie statt dessen lieber die using-Deklaration.

Die using-Deklaration dient dazu, einen speziellen Namen eines Namensbereichs in den aktuellen Gültigkeitsbereich aufzunehmen. Danach können Sie auf das Objekt durch einfache Angabe des Namens zugreifen.

Ein Alias für einen Namensbereich entspricht ungefähr einer typedef-Deklaration. Mit einem Alias können Sie einen zweiten Namen für einen benannten Namensbereich festlegen. Das ist besonders nützlich, wenn Sie Namensbereiche mit extrem langen Namen verwenden.

Jede Datei kann einen unbenannten Namensbereich enthalten. Ein unbenannter Namensbereich ist, wie der Name schon andeutet, ein Namensbereich ohne Namen. Ein unbenannter Namensbereich gibt Ihnen die Möglichkeit, die Namen innerhalb des Namensbereichs ohne Qualifizierung zu verwenden. Er trägt dafür Sorge, daß die Namen des Namensbereichs lokal zur Übersetzungseinheit sind. Unbenannte Namensbereiche sind das gleiche wie die Deklaration einer globalen Variablen mit dem Schlüsselwort static.

Die C++-Standardbibliothek ist von dem Namensbereich std eingeschlossen. Verwenden Sie möglichst nicht die using-Direktive zusammen mit der Standardbibliothek, sondern greifen Sie auf die using-Deklaration zurück.

Fragen und Antworten

Frage:
Muß ich Namensbereiche verwenden?

Antwort:
Nein. Insbesondere in einfachen Programmen können Sie auf Namensbereiche gänzlich verzichten. Verwenden Sie dann aber die alten Header-Dateien zur Standardbibliothek (z.B. #include <string.h>) und nicht die neuen (z.B. #include <cstring>).

Frage:
Welche zwei Anweisungen sind mit dem Schlüsselwort using möglich? Wo liegen die Unterschiede?

Antwort:
Das Schlüsselwort using kann für using-Direktiven und für using-Deklarationen verwendet werden. Die using-Direktive erlaubt es, alle Namen eines Namensbereichs wie normale Namen zu verwenden. Die using-Deklaration hingegen erlaubt es, einen bestimmten Namen eines Namensbereichs ohne Qualifizierung mit dem Namen des Namensbereichs zu verwenden.

Frage:
Was sind unbenannte Namensbereiche? Wozu werden sie eingesetzt?

Antwort:
Unbenannte Namensbereiche sind Namensbereiche ohne Namen. Sie werden verwendet, um eine Sammlung von Deklarationen »einzuhüllen« und so gegen mögliche Namenskonflikte zu schützen. Namen in einem unbenannten Namensbereich können nicht außerhalb der Übersetzungseinheit, in der der Namensbereich deklariert ist, verwendet werden.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Verständnis für die vorgestellten Themen zu festigen, und Übungen, anhand derer Sie lernen sollen, wie Sie das eben Gelernte anwenden können. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie die Lösung in Anhang D checken und zur Lektion des nächsten Tages übergehen.

Quiz

  1. Kann ich Namen, die in einem Namensbereich definiert sind, ohne vorangehende using-Anweisung verwenden?
  2. Was sind die Hauptunterschiede zwischen normalen und unbenannten Namensbereichen?
  3. Was versteht man unter dem Standardnamensbereich?

Übungen

  1. FEHLERSUCHE: Was ist falsch an diesem Programm?
    #include <iostream>

    int main()
    {
    cout << "Hello world!" << end;
    return 0;
    }
  2. Geben Sie drei Möglichkeiten an, das Problem in Übung 1 zu beheben.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH