Namespaces

Martin Kompf

Die aktive Verwendung von Namespaces erlaubt die bessere Strukturierung großer Softwareprojekte.

Namespaces werden von vielen Entwicklern nur passiv verwendet, insbesondere bei der Benutzung von Methoden aus der Standard Template Library (STL). In diesem Artikel soll gezeigt werden, wie die Deklaration und Benutzung eigener Namespaces vonstatten geht.

In einem Namespace können Deklarationen von Klassen, Funktionen, Templates und so weiter enthalten sein. Zum Beispiel:

namespace alpha
{
    class Database {
    public:
        void open();
        void close();
        // ...
    };
}

Hier wird der Namespace alpha definiert, der die Definition der Klasse Database enthält.

Warum Namespaces?

Der Sinn der Einführung eines Namespaces an dieser Stelle wird deutlich, wenn man sich vorstellt, dass alpha der Name eines Projektes sein könnte. Wird nun zu einem späteren Zeitpunkt ein zweites Projekt begonnen, welches ebenfalls eine Klasse Database enthalten soll, so wird diese Klassendefinition dann einfach in einen neuen Namespace beta gepackt:

namespace beta
{
    class Database {
    public:
        void open();
        void close();
    };
 
    class Connection {
    public:
        Database getDatabase( const char *name);
    };
}

Da die Definitionen der beiden unterschiedlichen Database Klassen auch in unterschiedlichen Namespaces existieren, können beide Klassen in ein und demselben Hauptprogramm verwendet werden. Die Unterscheidung erfolgt dabei durch Verwendung des Scope Resolution Operators ::

// voll qualifizierte Namen
alpha::Database adb;
beta::Database bdb;

Ohne die Verwendung von Namespaces würde es hier zu Kollisionen kommen.

Wege zur Implementierung

Die oben aufgeführten Definitionen von Klassen und Namespaces erfolgen typischerweise in Headerfiles, zum Beispiel adb.h. Dabei kann der gleiche Namespace sich über mehrere Dateien erstrecken, das heisst namespace alpha kann in mehreren Headerfiles stehen.

Was ist nun bei der Implementierung der Klasse Database zu beachten? Diese steht typischerweise in einer .cpp Datei, zum Beispiel adb.cpp. Vor die Implementierung der einzelnen Memberfunktionen der Klasse Database muss nun noch zusätzlich der Namespace notiert werden:

#include "adb.h"
 
void alpha::Database::open() { /* ... */ };
 
void alpha::Database::close() { /* ... */ };

Will man sich die damit verbundene Schreibarbeit sparen, so lässt sich mittels einer Using Direktive auch ein kompletter Namespace in den globalen Namensraum importieren. Dies macht in einer reinen Implementierungsdatei, wie zum Beispiel bdb.cpp, in welcher die Implementierungen der Klassen aus dem Namespace beta stehen, durchaus Sinn:

#include "bdb.h"
 
// using Direktive
using namespace beta;
 
void Database::open() { /* ... */ };
 
void Database::close() { /* ... */ };
 
Database Connection::getDatabase( const char *name)
{
    Database d;
    /* ... */
    return d;
};

Mit Vorsicht sollte man diese Using Direktiven natürlich im Hauptprogramm benutzen. Letztlich gehen bei exzessiver Verwendung von using namespace die Vorteile von Namespaces wieder verloren und es kann wieder zu Namenskonflikten kommen. Für den Schreibfaulen bietet sich hier noch ein Mittelweg an: Die Verwendung von Using Deklarationen. Damit werden nur einzelne Symbole aus einem Namspace in den globalen Namspace importiert. Zum Beispiel:

// using Deklaration
using beta::Connection;
 
Connection conn;
beta::Database bdb;
bdb = conn.getDatabase( "mydb");

Hier wird nur das Symbol Connection in den globalen Namespace importiert und kann daher anschließend ohne Namespaceprefix verwendet werden. Database wird hingegen nicht importiert, da es bei dieser Klasse ja zu Konflikten mit der im Namspace alpha definierten Klasse gleichen Namens kommen könnte.