Zugriff auf einen Namensraum

Wenn wir unsere Klassen nun in einen eigenen Namensraum legen, so müssen wir auch diesen Namensraum ansprechen können, um die Klassen verwenden zu können. Eine Klasse Token im Namensraum ChessGame liegt nunmal nicht im globalen Namensraum - im globalen Namensraum wird die Klasse Token also nicht gefunden.

Der Zugriff geht auch hier mit dem Operator '::':

namespace ChessGame
{
  class Token
  {
    private: 
      int Value;
 
    public: 
      Token( int value ) : Value( value ) {}
 
      static int staticFunc( int a, int b ) { return a+b; }
  };
}
 
int main( void )
{
  ChessGame::Token * myToken = new ChessGame::Token( 4711 );
 
  int addition = ChessGame::Token::staticFunc( 1, 2 ); 
 
  delete myToken;
  return 0;
}

An diesem Beispiel ist zu sehen, dass es keinen Unterscheid macht, ob man auf einen Namensraum zugreift oder auf statische Elemente einer Klasse.

Zugriff aus einem Namensraum heraus

Nun gibt es aber nicht nur den Fall, dass man in einen Namensraum hineingreifen möchte, sondern auch den Fall, dass man eine Klasse, Variable oder Funktion aus dem globalen Namensraum ansprechen möchte. Der globale Namensraum selbst hat keinen Namen, entsprechend schreibt man auch nichts vor den '::'-Operator:

int globalVariable;
 
namespace ChessGame
{
  int GetGlobalVariable( void )
  {
    // Zugriff auf eine Variable 
    // im globalen Namensraum 
 
    return ::globalVariable;   
  }  
}

Zugriff in einen anderen Namensraum

Die letzte Möglichkeit, die auftreten kann, ist, dass man in einen anderen Namensraum zugreifen möchte. Dabei ist die Betrachtung zunächst vom aktuellen Namespace aus und schaut dann schrittweise nach oben. Findet sich also im aktuellen Namespace die gewünschte Klasse, so wird diese genommen, ansonsten guckt man im Namespace darüber nach und arbeitet sich so bis zum globalen Namespace hoch.

#include <stdio.h>
 
namespace Internal
{
  class Token
  {
  protected:
    int Value;
 
  public:
    Token( int value ) : Value( value ) 
    {
      printf( "This is Internal::Token\n" );
    }
  };
}
 
namespace Compiler {
  namespace Internal
  {
    class Token
    {
      public:
        Token( int value )
        {
          printf( "This is Compiler::Internal::Token\n" );
        }
    };
  } 
 
  class Token : Internal::Token
  {
  public:
    Token( int token )
      : Internal::Token( token )
    {}
  };
 
}
 
int main( void )
{
  Compiler::Token * token = new Compiler::Token( 4711 );
 
  delete token;
 
  return 0;
}

Das Programm hier bietet eine gefährliche Situation, es gibt eine Klasse Internal::Token und es gibt eine Klasse Compiler::Internal::Token und die Klasse Compiler::Token muss nun entscheiden, von wem sie abgeleitet wird. Das ist so keineswegs wünschenswert, aber auch das ist in C++ eindeutig geregelt - man muss halt nur wissen, wie.

Wird das Programm kompiliert, so soll die Klasse Compiler::Token von der Klasse Internal::Token abgeleitet werden. Im Namensraum Compiler gibt es einen Namensraum Internal und darin die gesuchte Klasse Token. Also wird diese genommen. Wird das Programm ausgeführt, erhält man:

This is Compiler::Internal::Token

Möchten wir nun, dass die andere Klasse Internal::Token verwendet wird, bei der sich der Namensraum Internal im globalen Namensraum befindet, müssen wir klarstellen, dass sich der Namensraum auf den globalen Namensraum beziehen soll: ::Internal::Token. Das entspricht der absoluten Pfadangabe in einem Dateisystem (z.B. /home/xin unter Unix oder C:\Programme unter Windows). Entsprechend ändern wir die Compiler::Token-Klasse und setzen den '::'-Operator vor Internal:

  class Token : ::Internal::Token
  {
  public:
    Token( int token )
      : ::Internal::Token( token )
    {}
  };

Nun erhalten wir aus Ausgabe die Meldung aus dem ::Internal::Token-Konstruktor:

This is Internal::Token

Eine Verschachtelung wie in diesem Beispiel ist eigentlich nicht Ziel des ganzen, es geht in diesen Beispielen darum, die Funktionsweise zu demonstrieren. Normalerweise sollten die Namensräume eindeutig benannt werden. Aber dieses Beispiel zeigt, wie man aus einem Namensraum heraus in einen beliebigen anderen Namensraum verzweigen kann.

Der Namensraum std

std ist ein besonderer Namensraum, da hier alles abgelegt wird, was zu Standard-C++ gehört. So wird heutzutage printf auch nicht mehr mit <stdio.h> eingebunden, sondern mit <cstdio>. Der Unterschied ist, dass printf und die ganzen anderen Standard-Funktionen dann im Namensraum std angesiedelt sind und somit nicht mehr den globalen Namensraum vollmüllen:

#include <cstdio>
 
int main( void )
{
  std::printf( "Hello C++\n" );
}

Dies gilt für alle C-Header: cassert, cctype, cerrno, cfloat, climits, clocale, cmath, csetjmp, csignal, cstdarg, cstddef, cstdio, cstdlib, cstring, ctime ) hier muss nun bei allen Funktionen erst der Namensraum std (eigentlich ::std) vorangestellt werden.

In die Standardlib von C++ sind jedoch eine Vielzahl von zusätzlichen Headern gelangt, insbesondere die STL (Standard Template Library. Auch hier befindet sich alles unter ::std.

So kann eine Textausgabe unter C++ auch mit den sogenannten IOStreams durchgeführt werden:

#include <iostream>
 
int main( void )
{
  std::cout << "Hallo C++" << std::endl;
}

printf(), wie auch cout haben ihre Berechtigung. In printf lebt der prozedurale Ansatz weiter, mit cout wird klassenorientiert gedacht und die Daten werden einer Klasse zugeführt. Am Schluss landet in beiden Fällen der Text auf dem Bildschirm.