Namensräume
Bestimmte Namen für Funktionen und Klassen bieten sich einfach an und werden daher immer wieder verwendet. Um eine Kollision zu vermeiden (speziell bei der Verwendung verschiedener Bibliotheken, die man nicht immer abändern kann und will), kann man C++-Code in verschiedene Namensräume einpacken und so Eindeutigkeit schaffen. Das Thema ist heute mal wieder auf zwei Videos aufgeteilt, da es zu viel für ein einzelnes ist.
Videos
Namespaces allgemein
Anonmous und inline Namespaces
Code
Namensräume
|
|
Anonymous Namespaces
|
|
inline Namespaces
|
|
Erklärung
Ein Namespace ist erstmal ganz einfach definiert: namespace Name { }
. Alles, was innerhalb der geschweiften Klammern
steht gehört zu dem Namespace. Um große Namespaces effizient verwalten und auf mehrere Dateien verteilen zu können, kann man
sie jederzeit wieder öffnen. Eine weitere namespace
-Anweisung mit dem gleichen Namen fügt also einfach nur zusätzliche
Dinge in den Namensraum ein.
Namensräume können ineinander verschachtelt werden. Auf diese Art und Weise kann man bspw. alle Elemente einer größeren
Bibliothek in einen großen Namensraum zusammenfassen und dann weiter thematisch unterteilen. Ein Beispiel für die Anwendung
dieses Prinzips findet sich in der Boost-Bibliothek: alles liegt im Namensraum boost
und wird dann dort weiter unterteilt
je nach Unterbibliothek.
Will man auf einen Namen innerhalb eines Namespace zugreifen, so hat man mehrere Möglichkeiten: man kann ihn ausgehend vom
aktuellen Namespace (in welchem man sich selbst befindet) qualifizieren. Im Beispiel oben ist das bspw. in Zeile 44 oder 64
zu sehen. Zeile 44 wechselt vom Namespace N1
in den inneren Namespace Inner
und sucht dort nach dem Namen
callFromInner
. Der vollständige Name, nach dem also gesucht wird, ist ::N1::Inner::callFromInner
. Angegeben werden muss
natürlich nur der Teil ab dem Namespace, in dem man sich selbst befindet. Vergleichbar ist Zeile 64: dort wird der gleiche
Name gesucht, jetzt allerdings aus dem globalen, namenlosen Namespace. Daher muss N1
diesmal mit angegeben werden.
Will man aus einem Namespace heraus auf umgebende Namen zugreifen, dann gibt es zwei Möglichkeiten: standardmäßig sucht der
Compiler, wenn er einen Namen im eigenen Namespace nicht finden kann, so lang nach außen, bis er entweder was passendes
findet oder es nicht mehr weitergeht (was dann zu einem Fehler führt). Für den – seltenen – Fall, dass ein innerer Name
(bspw. im eigenen umgebenden Namespace) einen äußeren Namen verdecken würde, man aber auf den äußeren zugreifen will, gibt
es die Möglichkeit mittels ::
direkt ganz nach außen zu gehen. Zeile 50 demonstriert das vorgehen. Die globale Funktion
callPrint()
wird von dort aus gesehen durch die Funktion N1::callPrint()
verdeckt und ist ohne weiteres nicht
erreichbar. Durch den Aufruf ::callPrint()
können wir dem Compiler allerdings mitteilen, dass wir gern direkt ganz nach
außen gehen würden und von dort aus suchen.
Will man sich nun ein wenig Tipparbeit sparen und nicht immer die kompletten Namensräume (oder gar verschachtelte Ketten
davon) hinschreiben wollen. Hier gibt es zwei Möglichkeiten abzukürzen: einerseits kann man mittels namespace X = Y;
einen
Alias für namens X
für den Namensraum Y
vergeben, so dass immer, wenn man Y
schreiben müsste, man stattdessen nur X
schreiben braucht (unter der Annahme, das X
und Y
deutlich unterschiedlich lang sind). Andererseits kann man Namen eines
Namensraumes komplett in einen anderen importieren, indem man die Anweisung using namespace X;
verwendet. Danach sind alle
Namen aus X
in dem Namensraum, in dem man diese using
-Anweisung verwendet hat, verfügbar. Beide Varianten sollte man
sparsam einsetzen (speziell bei using mischt man ja wieder Namensräume und fängt sich potentiell Probleme mit
Namenskollisionen ein). Beides sollte man niemals in Header-Dateien verwenden (Aliases vielleicht, aber auch nur, wenn man
ganz genau weiß, was man tut.)
Anonymous Namespaces
Deutlich seltener, als die normalen Namespaces, braucht man anonyme Namensräume, also Namensräume ohne Namen. Klingt nach
einem eigenwilligen Konzept, kann aber in ganz bestimmten Situationen hilfreich sein. Beispiel 2 zeigt so eine Anwendung:
die Variable x
soll nur innerhalb der Datei global verfügbar sein (hier könnte man sich bspw. interne Helferfunktionen
oder ähnliches vorstellen). Wenn wir diese nun einfach so in die Datei schreiben und das vielleicht woanders nochmal tun,
dann stört das den Compiler nicht. Der Linker hingegen beschwert sich, weil er ein Symbol gleichen Namens mehrfach definiert
findet. Das können wir mit einem anonymen Namensraum beheben: hier vergibt der Compiler einen generierten, garantiert
eindeutigen Namen und importiert diesen direkt mittels using. So ist alles aus dem Namespace genauso verfügbar, als würde
der nicht existieren, dafür sind aber alle Namen darin garantiert eindeutig und führen nicht zu einer Kollision beim Linken.
inline-Namespaces
Inline-Namespaces lösen ein Problem, was nur die Entwickler von Bibliotheken haben dürften. Wenn man eine Bibliothek über
lange Zeit pflegt und weiterentwickelt, dann möchte man möglicherweise auch deren Schnittstelle weiterentwickeln.
Gleichzeitig möchte man (speziell bei kommerziellen Bibliotheken) aber den Nutzern die Möglichkeit geben, sich auf bestimmte
Schnittstellen zu verlassen. Zu diesem Zweck kann man Schnittstellen versionieren, indem man sie in verschiedene Namensräume
einpackt (oben im Beispiel 3 bspw. die erste Version im Namensraum Inner
, die zweite in V2
.) Den jeweils aktuellsten
Namensraum importiert man dann in dem umgebenden Namespace der Bibliothek und macht ihn so für alle als Schnittstelle direkt
sichtbar. Wer sich einfach auf die aktuellste Variante verlassen will, der kann direkt mittels Outer::
auf die Funktionen
zugreifen und wird auf die jeweils aktuellste Variante geleitet. Wer eine bestimmte Version braucht, kann diese mittels
Outer::Version::
qualifizieren und sich darauf verlassen, dass sich daran nichts ändert (wenn der Anbieter der Bibliothek
das verspricht).
Die beiden Varianten in Beispiel 3 verhalten sich von außen auf den ersten Blick identisch. Es gibt allerdings einen Randfall (für alle, die das schonmal nachlesen wollen: es geht um die Spezialisierung von Templates in Namensräumen), für den das zweite Beispiel fehlschlägt und den Nutzer zwingen würde, zu wissen um welchen genauen inneren Namensraum es gerade geht. Mit C++11 und den inline-Namespaces wurde hier Abhilfe geschaffen. Auch der Randfall muss hier korrekt funktionieren.