C++/Tutorial: 15. Namespaces

Aus Scientia
Version vom 23. Januar 2010, 18:47 Uhr von Ankou (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Nachdem wir bereits einen Großtteil der Kapitel hinter uns gelassen haben schauen wir uns unser Hello World Beispiel an und bemerken, dass wir immer noch nicht alles verstehen. Ich rede von der using Direktive, die wir bereits seit dem ersten Kapitel in Form von using namespace std;, using namespace std::tr1; und using namespace std::tr1::placeholders verwenden, aber noch nirgens genauer besprochen haben. using-Direktiven gehören zu dem Konzept, der Namensräume, das wir im Folgenden genauer betrachten wollen.

Namespaces

Viele Bibliotheken verwenden die selben Bezeichner für Klassen und Funktionen, ein Fenster wird häufig als Window bezeichnet, etc. Viele Bibliotheken führten deshalb die Verwendung von Prefixen ein(was sich auch heute noch durch viele C++ Bibliotheken zieht, MFC-Klassen beginnen mit C, Qt-Klassen mit Q, ClanLib mit CL_ und wxWidget mit wx) um Namenskonflikten vorzubeugen. Der C++ Standard führte dann in die Sprache das Konzept der Namensräume ein. Definieren wir etwas in einem Namensraum können wir es innerhalb des Namensraums alleine mit seinem Bezeichner ansprechen, außerhalb des Namensraums erfolgt der Zugriff über den Scope-Operator, den wir bereits bei statischen Membern von Klassen kennen gelernt haben.
Um etwas in einem Namespace zu definieren wird es von einem namespace bezeichner {} umschlossen. Dieser Block muss dabei keineswegs einzigartig sein, wie es z.B. bei Klassen innerhalb eines namespaces der Fall ist. Ein namespace kann sich auch über mehrere Dateien erstrecken. Definieren wir also einmal eine Funktion foo in einem namespace:

namespace my_super_cool_namespace
  {
  void foo()
    {
    //...
    }
  }

Wenn wir nun eine Funktion foo2 im selben namespace haben, kann diese foo ganz normal aufrufen:

namespace my_super_cool_namespace
  {
  void foo2()
    {
    foo();
    }
  }

wollen wir aber aus einem anderen namespace (auch aus dem globalen namespace) diese Funktion aufrufen erfolgt der Zugriff wie folgt:

my_super_cool_namespce::foo();

Es ist auch möglich namespaces zu schachteln

namespace some_namespace
  {
  namespace another_namespace
    {
    void foo()
      {
      //...
      }
    }
  }
//[...] Aufruf von außerhalb:
some_namspace::another_namespace::foo();

namespace aliases

Manchmal will man namespaces unter einem anderen Namen verfügbar machen, ähnlich typedefs für Typen. Hierfür bietet C++ die Möglichkeit der namespace aliases. Wollen wir z.B. den namespace some_namespace::another_namespace als sa_namespace aliasen sieht das wie folgt aus:

namespace sa_namespace = some_namespace::another_namespace;

sa_namespace ist damit synonym zu some_namespace::another_namespace zu verwenden.

Der globale namespace

Nehmen wir an, wir haben in dem namespace in dem wir uns gerade befinden eine Funktion foo und global ebenfalls eine Funktion foo. Rufen wir foo() auf, rufen wir dabei die Funktion foo() auf, die sich in unserem momentanem namespace befindet. Wollen wir jedoch die Funktion foo im globalen namespace aufrufen, müssen wir diesen irgendwie ansprechen. Das geschieht durch den Scope-Operator, ohne dass ein namespace vorangestellt wird. ::foo(); ruft dann also die Funktion foo im globalen namespace auf.

using-Direktiven

Die andere Seite des Konzeptes der Namensräume sind die using-Direktiven, die wir bereits kennen gelernt haben. Oftmals haben wir innerhalb unseres Programmes keine Namenskonflikte und wollen die Variablen, Funktionen, Klassen etc. des namespaces auch außerhalb dieses namespaces nutzen ohne den Scope-Operator benutzen zu müssen.
Das ist mit using-Direktiven möglich und wir haben es auch schon die ganze Zeit praktiziert. In der Tat ist std nichts weiter als ein namespace, indem sich die Standardbibliothek befindet mit using namespace std; erlauben wir uns den Zugriff auf std::string, std::cout etc. ohne vor jede Verwendung std:: schreiben zu müssen.
An dieser Stelle eine Anmerkung: using-Direktiven sollten niemals in Headerdateien stehen, denn damit werden sie logischerweise für alle Dateien gültig, die diesen Header inkludieren. Das Problem dabei ist, dass man aus dem Header heraus nicht sagen kann wo Namenskonflikte auftreten. Aus diesem Grund sollte der Zugriff auf andere namespaces (auch auf std) in Headern stets über den Scopeoperator geschehen.
An dieser Stelle noch ein Codebeispiel, für die mögliche Verwendung von some_namespace::another_namespace aus dem globalen namespace heraus:

using namespace some_namespace::another_namespace;
 
int main()
  {
  foo(); //ruft some_namespace::another_namespace; auf
  }

using namespace ist allerdings nicht die grundlegende using-Direktive. using namespace importiert den gesamten namespace, es ist aber auch Möglich einzelne Bezeichner zu importieren. Dafür verzichten wir auf das namespace in der Direktive und schreiben nur using Bezeichner. Wollen wir also nur die Funktion foo aus dem namespace some_namespace::another_namespace verfügbar machen, schreiben wir:

using some_namespace::another_namespace::foo;
 
int main()
  {
  foo();
  }