26 Espacios con nombre

Ya hemos usado espacios con nombre en los ejemplos, pero aún no hemos explicado por qué lo hacemos, qué significan o para qué sirven.

Un espacio con nombre, como indica su denominación, es una zona separada donde se pueden declarar y definir objetos, funciones y en general, cualquier identicador de tipo, clase, estructura, etc; al que se asigna un nombre o identificador propio.

Hasta ahora, en nuestros programas, siempre habíamos declarado y definido nuestros identificadores fuera de cualquier espacio con nombre, en lo que se denomina el espacio global. En este capítulo veremos que podemos crear tantos espacios para identificadores como necesitemos.

En cuanto a la utilidad los espacios con nombre, veremos nos ayudan a evitar problemas con identificadores en grandes proyectos o cuando se usan bibliotecas externas. Nos permite, por ejemplo, que existan objetos o funciones con el mismo nombre, declarados en diferentes ficheros fuente, siempre y cuando se declaren en distintos espacios con nombre.

Declaraciones y definiciones

Sintaxis para crear un espacio con nombre:

namespace [<identificador>] {
...
<declaraciones y definiciones>
...
}

Veamos un ejemplo:

// Fichero de cabecera "puntos.h"
namespace espacio_2D {
   struct Punto {
      int x;
      int y;
   };
}

namespace espacio_3D {
   struct Punto {
      int x;
      int y;
      int z;
   };
}// Fin de fichero

Este ejemplo crea dos versiones diferentes de la estructura Punto, una para puntos en dos dimensiones, y otro para puntos en tres dimensiones.

Sintaxis para activar un espacio para usar por defecto, esta forma se conoce como forma directiva de using:

using namespace <identificador>;

Ejemplo:

#include "puntos.h"
using namespace espacio_2D; // Activar el espacio con nombre espacio_2D

Punto p1;             // Define la variable p1 de tipo espacio_2D::Punto
espacio_3D::Punto p2; // Define la variable p2 de tipo espacio_3D::Punto
...

Sintaxis para activar un identificador concreto dentro de un espacio con nombre, esta es la forma declarativa de using:

using <nombre_de_espacio>::<identificador>;

Ejemplo:

#include "puntos.h"

using espacio_3D::Punto; // Usar la declaración de Punto
                         // del espacio con nombre espacio_3D
...

Punto p2;             // Define la variable p2 de tipo espacio_3D::Punto (el espacio activo)
espacio_2D::Punto p1; // Define la variable p1 de tipo espacio_2D::Punto
...

Sintaxis para crear un alias de un espacio con nombre:

namespace <alias_de_espacio> = <nombre_de_espacio>;

Ejemplo:

namespace nombredemasiadolargoycomplicado {
...
declaraciones
...
}
...
namespace ndlyc = nombredemasiadolargoycomplicado; // Alias
...

Utilidad

Este mecanismo permite reutilizar el código en forma de bibliotecas, que de otro modo no podría usarse. Es frecuente que diferentes diseñadores de bibliotecas usen los mismos nombres para cosas diferentes, de modo que resulta imposible integrar esas bibliotecas en la misma aplicación.

Por ejemplo un diseñador crea una biblioteca matemática con una clase llamada "Conjunto" y otro una biblioteca gráfica que también contenga una clase con ese nombre. Si nuestra aplicación incluye las dos bibliotecas, obtendremos un error al intentar declarar dos clases con el mismo nombre.

El nombre del espacio funciona como un prefijo para las variables, funciones o clases declaradas en su interior, de modo que para acceder a una de esas variables se tiene que usar el operador de especificador de ámbito (::), o activar el espacio con nombre adecuado.

Por ejemplo:

#include <iostream>

namespace uno {
int x;
}

namespace dos {
int x;
}

using namespace uno;

int main() {
   x = 10;
   dos::x = 30;

   std::cout << x << ", " << dos::x << std::endl;
   std::cin.get();
   return 0;
}

En este ejemplo hemos usado tres espacios con nombre diferentes: "uno", "dos" y "std". El espacio "std" se usa en todas las bibliotecas estándar, de modo que todas las funciones y clases estándar se declaran y definen en ese espacio.

Hemos activado el espacio "uno", de modo que para acceder a clases estándar como cout, tenemos que especificar el nombre: std::cout.

También es posible crear un espacio con nombre a lo largo de varios ficheros diferentes, de hecho eso es lo que se hace con el espacio std, que se define en todos los ficheros estándar.

Espacios anónimos

¿Espacios con nombre sin nombre?

Si nos fijamos en la sintaxis de la definición de un espacio con nombre, vemos que el nombre es opcional, es decir, podemos crear espacios con nombre anónimos.

Pero, ¿para qué crear un espacio anónimo? Su uso es útil para crear identificadores accesibles sólo en determinadas zonas del código. Por ejemplo, si creamos una variable en uno de estos espacios en un fichero fuente concreto, la variable sólo será accesible desde ese punto hasta el final del fichero.

namespace Nombre {
   int f();
   char s;
   void g(int);
}

namespace {
   int x = 10;
}
// x sólo se puede desde este punto hasta el final del fichero
// Resulta inaccesible desde cualquier otro punto o fichero

namespace Nombre {
   int f() {
      return x;
   }
}

Este mecanismo nos permite restringir el ámbito de objetos y funciones a un fichero determinado dentro de un proyecto con varios ficheros fuente. Si recordamos el capítulo anterior, esto se podía hacer con el especificador static aplicado a funciones o a objetos globales.

De hecho, la especificación de C++ aconseja usar espacios con nombre anónimos para esto, en lugar del especificador static, con el fin de evitar la confusión que puede producir este doble uso del especificador.

Espacio global

Cualquier declaración hecha fuera de un espacio con nombre pertenece al espacio global.

Precisamente porque las bibliotecas estándar declaran todas sus variables, funciones, clases y objetos en el espacio std, es necesario usar las nuevas versiones de los ficheros de cabecera estándar: "iostream", "fstream", etc. Y en lo que respecta a las procedentes de C, hay que usar las nuevas versiones que comienzan por 'c' y no tienen extensión: "cstdio", "cstdlib", "cstring", etc...". Todas esas bibliotecas han sido rescritas en el espacio con nombre std.

Si decidimos usar los ficheros de cabecera de C en nuestros programas C++, estaremos declarando las variables, estructuras y funciones en el espacio global.

Espacios anidados

Los espacios con nombre también se pueden anidar:

#include <iostream>

namespace uno {
   int x;
   namespace dos {
      int x;
      namespace tres {
         int x;
      }
   }
}

using std::cout;
using std::endl;
using uno::x;

int main() {
   x = 10; // Declara x como uno::x
   uno::dos::x = 30;
   uno::dos::tres::x = 50;

   cout << x << ", " << uno::dos::x <<
      ", " << uno::dos::tres::x &lt;&lt; endl
   std::cin.get();
   return 0;
}

Palabras reservadas usadas en este capítulo

namespace y using.