Namensbereiche sind neu in ANSI C++. Sie helfen den Programmierern, Namenskonflikte zu vermeiden, wenn mehr als eine Bibliothek verwendet wird. Heute lernen Sie,
std
eingesetzt wird.
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.
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 VariableintegerWert
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.
Verwenden Sie möglichst nicht das
Schlüsselwort |
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.
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 ) ;
}
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.
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.
// 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
}
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 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 ;
}
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
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 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 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.
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.
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.
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.
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.
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.
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.
using
-Anweisung verwenden?
#include <iostream>
int main()
{
cout << "Hello world!" << end;
return 0;
}
© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH