C++ Kurs

Namensräume

Die Themen:

In diesem Kapitel wollen wir uns mit den Namensräumen 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 Ungewöhnliches.

PgmHeader// Definitionen
int var;
class Any
{....};
// main() Funktion
int main()
{
    Any myObj;
    var = 10;
    ....
}

Nehmen wir jetzt einmal an, wir wollen unser Programm erweitern und dazu eine gekaufte oder frei verfügbare Klassenbibliothek einsetzen. Zu dieser Klassenbibliothek gehört natürlich auch eine Header-Datei (im Beispiel clib.h), die auszugsweise unten dargestellt ist. In dieser Header-Datei sind aber unglücklicherweise ebenfalls eine Klasse Any und eine Variable var definiert. Was nun folgt, lässt sich schon erahnen.

PgmHeader// Headerdatei der Klassenbibliothek clib
#ifndef CLIB_H
#define CLIB_H
int var;
class Any
{....};
#endif

Definition eines Namensraums

Um die Klassenbibliothek im Programm verwenden zu können, muss selbstverständlich die Header-Datei clib.h eingebunden werden. Und damit lässt sich das Programm nicht mehr fehlerfrei übersetzen, da sowohl in der Header-Datei clib.h als auch im Programm selbst die Variable var und die Klasse Any definiert sind. Ein erster Lösungsansatz könnte nun darin bestehen, 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. Wird auch nur ein einziges Vorkommen von var im Programm nicht umbenannt, dann wird auf die falsche Variable zugegriffen.

PgmHeader#include "clib.h"
// Definitionen
int var;        // Das erzeugt jetzt Fehler!
class Any
{....};
// main() Funktion
int main()
{
    Any myObj;
    var = 10;
    ....
}

Und genau für solche Fälle wurden die Namensräume geschaffen. Die allgemeine Syntax zur Definition eines Namensraums lautet:

namespace [NNAME]
{
   .... // Definitionen und Deklarationen
}

NNAME definiert den Namen des Namensraums. Und alle in einem Namensraum stehenden Definitionen und Deklarationen sind auch nur innerhalb dieses Namensraums gültig! Gleichnamige Definitionen und Deklarationen in unterschiedlichen Namensräumen erzeugen somit keinen Namenskonflikt mehr.

Anwendung von Namensräumen

Um die Namenskonflikte mit Hilfe von Namensräumen in unserem Beispiel aufzulösen, stehen zwei Lösungswege zur Verfügung. Im ersten Fall wird die Header-Datei clib.h in einen eigenen Namensraum gelegt und im zweiten Fall die Definitionen und Deklarationen der Anwendung. Beide Lösungen sind von der Wirkungsweise gleichwertig. Da nun die Definitionen von var und Any in verschiedenen Namensräumen erfolgen,  kommt es keinen Namenskonflikten mehr.

PgmHeaderLösung 1:
namespace clib
{
    #include "clib.h"
}
int var;
class Any
{....};

Lösung 2:
#include "clib.h"
namespace cppkurs
{
    int var;
    class Any
    {....};
}

Wird ein bereits bestehender Namensraum erneut definiert, so wird der bisherige Namensraum um die Deklarationen und Definitionen erweitert. Im Beispiel enthält der Namensraum cppkurs alle Deklarationen und Definitionen aus den beiden Header-Dateien mylib.h und yourlib.h.

PgmHeader// Header-Datei mylib.h
namespace cppkurs
{
   ...
}

// Header-Datei yourlib.h
namespace cppkurs
{
   ...
}

Zugriff auf Namensräume

Sehen wir uns jetzt die Zugriffe auf Definitionen und Deklarationen in den Namensräumen an. Hierzu gibt es ebenfalls verschiedene Wege. Eine Lösung besteht darin, dass beim Zugriff zuerst der jeweilige Namensraum, dann der Zugriffsoperator :: und danach die entsprechende Deklaration bzw. Definition angegeben wird. Im Beispiel wurde die Header-Datei clib.h in einen eigenen Namensraum clib gelegt. Wie dann auf die darin enthaltene Variable var zugegriffen und ein Objekt der darin enthaltenen Klasse Any zu definieren ist, ist ebenfalls dargestellt.

PgmHeadernamespace clib
{
    #include "clib.h"
}
int var;      // Eigene Definitionen
class Any
{....};
// 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::DNAME;

NNAME ist der Name des Namensraums und DNAME der Name der jeweiligen Deklaration bzw. Definition. Diese 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.

PgmHeader// 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 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.

In einem Programm können durchaus auch mehrere Namensräume mittels using namespace eingeblendet werden. Diese Namensräume sind dann additiv, d.h. es sind alle Member aus den Namenräumen gültig. Soll dieses additive Verhalten eingeschränkt werden, so ist die using-Anweisung in einem Block {...} einzuschließen. Wie bereits vorhin erwähnt, endet das Einblenden eines Namensraums immer an der Grenze des aktuellen Blocks.

PgmHeaderusing namespace std;
using namespace cppkurs;
// Ab hier können alle Member des Namensraums
// std und cppkurs direkt verwendet werden

....

Geschachtelte und anonyme Namensräume

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.

PgmHeadernamespace outer
{
    namespace inner
    {
        int var;
    }
}
// Expliziter Zugriff auf var
outer::inner::var = 10;

Mit Hilfe eines anonymen Namensraums, d.h. ein Namensraum ohne Name, können Variablen, Funktionen usw. einer 'Übersetzungseinheit' (in der Regel ist dies eine Quelldatei) nach außen hin verborgen werden. Innerhalb der Übersetzungseinheit kann weiterhin auf alle Member des Namensraums wie gewohnt zugegriffen werden, aber außerhalb der Übersetzungseinheit besteht keine Möglichkeit, diese Member anzusprechen. 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.

PgmHeadernamespace
{
    int var;
    ....
    void Func1(...)
    {
        var = 10;
        ....
    }
    ....
}

Und noch Hinweis: Namensräume dürfen nicht innerhalb von Blöcken definiert werden. Die einzige Ausnahme dabei bilden die vorhin erwähnten geschachtelten Namensräume.

PgmHeader// Das geht nicht!
...
void Func(...)
{
    namespace wrong
    {
        ....
    }
}

inline Namensräume

Ein inline Namensraum ist ein Namensraum, welcher innerhalb eines anderen Namensraums definiert wird. Das kennen wir ja schon von den geschachtelten Namensräumen her. Zusätzlich steht jetzt aber vor dem Schlüsselwort namespace das Schlüsselwort inline. Damit ergibt sich folgender prinzipieller Aufbau:

PgmHeadernamespace myNamespace
{
    inline namespace newVersion
    {
        ....
    }
}

Auf alle in einem inline Namensraum deklarierten bzw. definierten Member kann nun ohne explizite Angabe des Namens des inline Namensraums zugegriffen werden.

Wofür ein solcher inline Namensraum sinnvoll eingesetzt werden kann, sehen wir uns an einem kleinen Beispiel an. Nehmen wir einmal an, im Namensraum myLibs sei eine Funktion PrintValue() definiert.

PgmHeader// Umgebender Namensraum
namespace myLibs
{
    void PrintValue(int x)
    {
        cout << "Std-Print():" << x << endl;
    }
}

int main()
{
    MyLibs::PrintValue(5);
}

Diese Funktion soll nun, aus welchen Gründen auch immer, modifiziert werden. Jedoch soll die ursprüngliche Funktion aus Kompatibilitätsgründen weiterhin erhalten bleiben und vom Anwender bei Bedarf auch explizit aufgerufen werden können. Das Problem, das sich hier nun ergibt, ist, dass die alte und die neuen Funktion die gleiche Signatur (Funktionsname und -parameter) haben und sie damit wegen des Namenskonflikts nicht im gleichen Namensraum liegen dürfen. Um dieses Problem zu lösen, werden zunächst zwei neue Namensräume eingefügt. Ein Namensraum enthält die alte Funktion und der anderen Namensraum die neue Funktion. Damit der Anwender standardmäßig die neue Funktion verwendet, wird der Namensraum der neuen Funktion als inline Namensraum definiert. Und wie Sie bereits erfahren haben, kann dieser Name des inline Namensraums beim Aufruf der neuen Funktion weggelassen werden.

PgmHeader// Umgebender Namensraum
namespace myLibs
{
    // Version 1 der PrintValue Funktion
    namespace V1
    {
        void PrintValue(int x)
        {
            cout << "Std-Print():" << x << endl;
        }
    }
    // inline Namensraum mit neuer Version 2 der PrintValue Funktion
    inline namespace V2
    {
        void PrintValue(int x)
        {
            cout << "New-Print():" << x << endl;
        }
    }
}

int main()
{
    MyLibs::PrintValue(5);              // Funktion V2 aufrufen
    MyLibs::V1::PrintValue(10);         // Funktion V1 aufrufen
}

Soll standardmäßig die alte Funktion verwendet werden, so ist lediglich der Namensraum V1 anstelle des Namensraums V2 als inline Namensraum zu definieren.

So und das war's auch schon. Dieses mal ohne Beispiel und ohne Übung.