Namespaces wurden 1995 in den C++-Standard aufgenommen und werden üblicherweise wie folgt definiert:
Ein Namespace definiert einen neuen Gültigkeitsbereich und bietet eine Möglichkeit, Namenskollisionen zu vermeiden.
Namespaces werden in C++ vor allem eingesetzt, um Namenskollisionen zu vermeiden. Während sie in modernem C++-Code intensiv genutzt werden, kommt dieses Sprachmittel in älterem Code häufig noch nicht zum Einsatz.
Nach der Analyse des Quellcodes zahlreicher C++-Projekte haben wir einige der häufigsten Gründe zusammengestellt, warum Namespaces in solchen Projekten verwendet werden.
1. Namenskollisionen vermeiden
Wie bereits erwähnt, ist dies der häufigste Grund. In diesem Fall kommt ihre Verwendung in erster Linie dem Compiler zugute; für Entwickler wird der Code dadurch nicht zwangsläufig lesbarer oder wartbarer.
2. Die Anwendung modularisieren
Moderne C++-Bibliotheken nutzen Namespaces intensiv, um ihre Codebasis zu modularisieren. Häufig kommt dabei der Ansatz „Namespace pro Feature“ zum Einsatz. Dabei bilden Namespaces die funktionale Gliederung der Software ab: Alle Elemente, die zu einer bestimmten Funktion gehören – und ausschließlich diese – werden in einem gemeinsamen Namespace zusammengefasst. Das führt zu Namespaces mit hoher Kohäsion und Modularität sowie zu einer möglichst geringen Kopplung untereinander. Elemente, die eng zusammenarbeiten, befinden sich somit auch strukturell nahe beieinander.
Boost ist ein hervorragendes Beispiel für eine Gliederung nach Funktionen: Die Bibliothek enthält Tausende von Namespaces, von denen jeder einen bestimmten Funktionsbereich zusammenfasst.
3. Anonyme Namespaces
Ein unbenannter Namespace stellt eine Alternative zu globalen statischen Variablen dar. Auf einen anonymen Namespace kann ausschließlich innerhalb der Datei zugegriffen werden, in der er deklariert wurde.
4. Workaround für das Enum-Problem
„Traditionelle“ Enums in C++ bringen ihre Enumeratoren in den umgebenden Gültigkeitsbereich ein. Dadurch können Namenskollisionen entstehen, wenn zwei unterschiedliche Enums im selben Gültigkeitsbereich Enumeratoren mit identischem Namen definieren.
In einem großen Projekt lässt sich nicht garantieren, dass zwei verschiedene Enums nicht denselben Enumeratornamen verwenden. Dieses Problem wurde mit C++11 durch enum class, gelöst. Dabei werden die Enum-Werte implizit dem Gültigkeitsbereich des Enum-Namens zugeordnet.
Vor C++11 bestand ein verbreiteter Workaround darin, ein Enum innerhalb eines Namespace zu deklarieren. Anstatt beispielsweise ein Enum wie folgt zu deklarieren:
enum status{
status_ok,
status_error
};konnte es innerhalb eines Namespace deklariert werden:
namespace status{
enum status{
ok,
error
};
}Viele C++-Projekte verwenden diesen Trick. Im Quellcode der Unreal Engine beispielsweise kommt diese Technik häufig zum Einsatz.
5. Implementierungsdetails per Konvention verbergen
Bei Template-Bibliotheken, deren Code in Headerdateien implementiert ist, ist es sinnvoll, den Nutzern der Bibliothek kenntlich zu machen, dass bestimmte Typen reine Implementierungsdetails sind und nicht direkt verwendet werden sollten. In C# erfüllt das Schlüsselwort „internal“ diesen Zweck; in C++ gibt es jedoch keine entsprechende Möglichkeit, öffentliche Typen vor Bibliotheksnutzern zu verbergen.
Ein verbreitetes Idiom im modernen C++, das insbesondere von den Entwicklern der Boost-Bibliotheken etabliert wurde, besteht darin, Symbole, die zur Implementierung eines Moduls gehören – also nicht Teil der öffentlichen API sind –, aber dennoch öffentlich sichtbar sein müssen, in einen separaten Unter-Namespace auszulagern. Dieser trägt üblicherweise den Namen detail.
In der Dokumentation von Boost.Math heißt es beispielsweise:
Funktionen, die nicht für die Verwendung durch Anwendungen vorgesehen sind, befinden sich in boost::math::detail.