Hola, a todos sean bienvenidos a mi nuevo post. Hoy hablaremos sobre los espacios de nombre (namespaces), habitualmente se tienen algunos inconvenientes por la repeticion de alguna variable o funcion, la cual al compilar nuestro programa se nos puede indicar la duplicidad de los mismos y para evitar esto utilizamos los namespace. Estos son utilizados para particionar el espacio de nombres globales y eliminar, o por lo menos reducir, los conflictos de nombre, la estructura es muy similar a la de clases, asi como su sintaxis, veamos algunos de sus atributos:

  • Los elementos declarados dentro del espacio de nombres son propiedad del mismo.
  • Todos los elementos son de visibilidad publica
  • Los espacios de nombres se pueden anidar dentro de otros espacios de nombres
  • Se puede definir funciones dentro o fuera del cuerpo del mismo
  • Si se define fuera debe ser identificada por el nombre del espacio de nombres

Los problemas de nombre pueden ser resueltos por el compilador pero en algunas circunstancias este esperara al enlazador para ver si el puede resolverlo o sera el encargado de notificarnos de la falla, Supongamos el siguiente codigo:

// archivo: primero.cxx

int valorEntero = 0;
int maint()
{
int valorEntero = 0;
;//….
;return 0;
}

//archivo: segundo.cxx
int valorEntero = 0;
//fin del segundo.cxx

Si nosotros utilizaramos el enlazador GNU, esta nos devolveria el siguiente mensaje:

segundo.cc: multiple defitinon of ‘valorEntero’
primero.cc: primero defined here

Hasta ahora hemos utilizado la funcion namespace en nuestro programas pero sin dar una explicacion sobre la misma, en el siguiente ejemplo veremos porque utilizabamos el namespace en nuestros ejemplo y hablaremos sobre los tipos de enlace en los programas:

//archivo: primero.cxx
extern const int j = 10;

//archivo: segundo.cxx
extern const int j;
#include

int main()
{
std::cout << “j vale ” << j << std::endl;
return 0;
}

Si nosotros ejecutaramos este codigo deberiamos obterner una salida de este estilo:

j vale 10

Existen dos tipos de enlace, internos y externos, los de indole internos son aquellos que son utilizados dentro de la misma libreria y los externos son los utilizados mas alla de la libreria original. Como pueden ver gracias a la declaracion de j como const y a su vez como externa, el programa sabe el valor de j desde el archivo primero.cxx y en el archivo segundo.cxx no fue necesario declararle el valor porque lo toma desde el otro archivo. Si observan se antepone std:: delante de cout y endl, esto en realidad lo deberiamos haber hecho en todos nuestros codigos fuentes anteriores pero como siempre utilizamos la siguiente linea:

using namespacce std;

Esto le indicaba al programa la implemtacion automatica de este indicador para este tipo de funciones, asumiendo que todos las funciones de iostream tienen antepuesto el indicador de namespace, traten de cualquier codigo anterior eliminar o marcarla con // adelante y al compilarla van a obterner el siguiente error:

$ g++ source/flujos15.cpp -o flujos15
source/flujos15.cpp: In function ‘int main(int, char**)’:
source/flujos15.cpp:7:1: error: ‘cout’ was not declared in this scope
cout << "Se recibieron " << argc << " argumentos...\n";
^~~~
source/flujos15.cpp:7:1: note: suggested alternative:
In file included from source/flujos15.cpp:1:0:
/usr/include/c++/6/iostream:61:18: note: ‘std::cout’
extern ostream cout; /// Linked to standard output
^~~~
source/flujos15.cpp:9:50: error: ‘endl’ was not declared in this scope
cout << "Argumento " << i << ": " << argv[i] << endl;
^~~~
source/flujos15.cpp:9:50: note: suggested alternative:
In file included from /usr/include/c++/6/iostream:39:0,
from source/flujos15.cpp:1:
/usr/include/c++/6/ostream:590:5: note: ‘std::endl’
endl(basic_ostream<_CharT, _Traits>& __os)
^~~~

 

Observen como nos sugiere agregar el std::cout o std::endl en cada una de las lineas donde esta involucrada, hasta hemos visto algunas fallas y como hemos utilizado, sin saberlo, el namespace en nuestros programas, veamos como se declara un nuevo namespace:

namespace [nombre del mismo]
{
  // funciones
}

Como pueden ver es similar a como definimos una clase, donde introduciremos un nombre para identificarlo y creamos entre llaves un bloque donde se almacenaran las funciones, veamos a traves del siguiente ejemplo como funcionan un namespace:

namespace00.cpp

# include <iostream>

namespace Ventana
{
const int MAX_X = 30;
const int MAX_Y = 40;

class Vidrio
{
public:
Vidrio():x(0),y(0){}
~Vidrio(){}
void tamanio(int x, int y);
void mover(int x, int y);
void mostrar();
private:
static int cnt;
int x;
int y;
};
}

int Ventana::Vidrio::cnt = 0;

void Ventana::Vidrio::tamanio(int x, int y)
{
if (x < Ventana::MAX_X && x > 0)
Vidrio::x = x;
if (y < Ventana::MAX_Y && y > 0)
Vidrio::y = y;
}

void Ventana::Vidrio::mover(int x, int y)
{
if (x < Ventana::MAX_X && x > 0)
Vidrio::x = x;
if (y < Ventana::MAX_Y && y > 0)
Vidrio::y = y;
}

void Ventana::Vidrio::mostrar()
{
std::cout << ” x: ” << Vidrio::x;
std::cout << ” y: ” << Vidrio::y << std::endl;
}

int main()
{
Ventana::Vidrio vidrio;

vidrio.mover(20, 20);
vidrio.mostrar();
return 0;
}

Lo primero diferente a nuestros programas anteriores es la falta de la utilizacion de la siguiente linea:

using namespace std;

Luego crearemos un namespace, al cual llamaremos Ventana, en este namespace crearemos dos variables MAX_X y MAX_Y, luego crearemos una clase llamada Vidrio, en esta tendremos el constructor y destructor, luego crearemos los prototipos de los metodos de Vidrio, mover(), tamanio() y mostrar(), todo esto en public, en private crearemos tres variables, una estatica, y dos de tipo entero. Luego salimos de la clase y del namespace, la primera linea declara el valor de la variable estatica:

int Ventana::Vidrio::cnt = 0;

Como pueden ver la forma de llegar a esa variable es a traves de toda la ruta donde esta ubicada, primero el tipo de variable, luego el nombre del namespace, luego la clase y por ultimo el nombre de la variable, si hubiera mas clases se tendria que agregar las mismas, analicemos el siguiente bloque:

void Ventana::Vidrio::tamanio(int x, int y)
{
if (x < Ventana::MAX_X && x > 0)
Vidrio::x = x;
if (y < Ventana::MAX_Y && y > 0)
Vidrio::y = y;
}

En este bloque vemos como se define el metodo tamanio(), observen como se indica toda la ruta, es decir namespace y la clase dentro de la misma, donde tendremos un condicional que chequea si x es menor al valor constante de MAX_X ubicado dentro del namespace Ventana y si x es mayor a cero, asigna el nuevo valor a la variable x dentro de  la clase Vidrio, el siguiente condicional hace exactamente lo mismo pero para la variable y. El siguiente bloque trabaja de la misma forma pero es la relacionado a mover() pero es exactamente a la explicada anteriormente porque tambien vuelve a definir los valores de x e y, Luego tendremos la funcion mostrar() donde su unica accion es mostrar los valores de x e y en pantalla. Si pueden ver, como nosotros no declaramos la linea para llamar a la libreria estandar, debemos indicarlo adelante de cada uno de las funciones cout para mostrar en pantalla los valores de x e y. Luego vamos a main(), en ella crearemos un objeto llamado vidrio, despues de ese objeto utilizaremos mover() donde le definiremos los valores de x e y a 20, luego utilizamos mostrar() y por ultimo salimos del programa, esta es la salida del programa:

$ ./program/namespace00
x: 20 y: 20

Hasta aqui un codigo simple para ver como funciona namespace, ahora pasaremos a ver como se utiliza using. using es una palabra reservada con dos funciones, puede ser una directiva o una declaracion, cuando utilizamos el using como directiva se permite el acceso a todos los nombres declarados en el mismo, en cambio como declaracion permite un acceso mas especifico al nombre al cual se debe facilitar el acceso. Hablemos un poco mas del tipo directiva, en directiva es como estabamos utilizando hasta ahora cuando llamabamos a using namespace std, porque de esta forma todos los nombres contenidos en el namespace no necesitabamos declarar el origen de los mismos, using puede ser declarado en cualquier nivel pero el alcance siempre va a estar restringido al bloque donde fue declarado y fuera del mismo pierde toda su validez, por esto si lo declaramos como global, el using estara en todos los bloques en cambio si lo declaramos dentro de main() estara solamente en este bloque. Veamos el ejemplo anterior pero utilizando el using en el mismo:

namespace01.cpp

# include <iostream>

namespace Ventana
{
const int MAX_X = 30;
const int MAX_Y = 40;

class Vidrio
{
public:
Vidrio():x(0),y(0){}
~Vidrio(){}
void tamanio(int x, int y);
void mover(int x, int y);
void mostrar();
private:
static int cnt;
int x;
int y;
};
}

using namespace Ventana;

int Vidrio::cnt = 0;

void Vidrio::tamanio(int x, int y)
{
if (x < MAX_X && x > 0)
Vidrio::x = x;
if (y < MAX_Y && y > 0)
Vidrio::y = y;
}

void Vidrio::mover(int x, int y)
{
if (x < MAX_X && x > 0)
Vidrio::x = x;
if (y < MAX_Y && y > 0)
Vidrio::y = y;
}

void Vidrio::mostrar()
{
std::cout << ” x: ” << Vidrio::x;
std::cout << ” y: ” << Vidrio::y << std::endl;
}

int main()
{
Vidrio vidrio;

vidrio.mover(20, 20);
vidrio.mostrar();
return 0;
}

El ejemplo es exactamente lo mismo al anterior pero la diferencia esta en la linea despues de declarado el namespace, donde utilizamos el using:

using namespace Ventana;

Despues podemos observar como no es necesario declarar el nombre del namespace, les muestro un par de ejemplos:

Sin el using:

int Ventana::Vidrio::cnt = 0;

Con el using:

int Vidrio::cnt = 0;

Sin el using:

void Ventana::Vidrio::tamanio(int x, int y)
{
……
}

Con el using:

void Vidrio::tamanio(int x, int y)
{
……
}

Sin el using:

Ventana::Vidrio vidrio;

Con el using:

Vidrio vidrio;

Como pueden ver usar el using como directiva nos permite omitir el nombre del namespace porque el programa entiende que cada nombre proveniente del namespace debe utilizarlo implicitamente, ahora vamos a utilizar a using como declaracion para ver como funciona, como antes modificaremos el primer ejemplo para ver su funcionamiento:

namespace02.cpp

# include <iostream>

namespace Ventana
{
const int MAX_X = 30;
const int MAX_Y = 40;

class Vidrio
{
public:
Vidrio():x(0),y(0){}
~Vidrio(){}
void tamanio(int x, int y);
void mover(int x, int y);
void mostrar();
private:
static int cnt;
int x;
int y;
};
}

using namespace std;
using Ventana::MAX_X;
using Ventana::MAX_Y;

int Ventana::Vidrio::cnt = 0;

void Ventana::Vidrio::tamanio(int x, int y)
{
if (x < MAX_X && x > 0)
Vidrio::x = x;
if (y < MAX_Y && y > 0)
Vidrio::y = y;
}

void Ventana::Vidrio::mover(int x, int y)
{
if (x < MAX_X && x > 0)
Vidrio::x = x;
if (y < MAX_Y && y > 0)
Vidrio::y = y;
}

void Ventana::Vidrio::mostrar()
{
cout << ” x: ” << Vidrio::x;
cout << ” y: ” << Vidrio::y << endl;
}

int main()
{
Ventana::Vidrio vidrio;

vidrio.mover(20, 20);
vidrio.mostrar();
return 0;
}

En este caso utilice using primero como directiva y despues dos como declaraciones, la directiva es la vuelta de nuestra vieja linea invocando a std, y luego si le decimos que declare unicamente el acceso a las dos variables constantes, observen como lo unico modificado van a ser cuando debemos utilizar las constantes MAX_X y MAX_Y dentro de los condicionales de los metodos tamanio() y mover(), tambien desaparecieron el std:: del metodo mostrar(). Otro metodo aceptable para el espacio de nombre, es utilizar un alias, el alias puede ser utilizado para reemplazar el nombre del espacio de nombre, una forma de declararlo seria de la siguiente forma:

namespace la_compania_de_software {
int valor;
}

la_compania_de_software::valor = 10;

namespace LCS = la_compania_de_software;
LCS::valor = 20;

Como pueden ver el metodo es simple, luego de declarado el namespace volvemos a crear uno nuevo con otro nombre pero esta vez le agregamos el igual y luego le ingresamos el nombre del namespace al cual va a equivaler, y despues de esto puede ser usado como si fuera la clase original. El unico inconveniente es la posibilidad de un conflicto del alias con algun nombre existente pero en este caso el compilador se encargara de reemplazar el nombre del alias con el original. Ahora hablaremos sobre otro metodo de utilizar el namespace, sin nombre. El namespace sin un nombre es utilizado para proteger los datos globales sobre potenciales conflictos, tambien se lo utiliza porque su comportamiento es igual a un objeto static con enlace externo. Ahora finalmente hablaremos sobre la libreria standard, esta tiene almacenado todos la funciones, clases, objetos y plantillas, se declara como lo vinimos haciendo hasta ahora con using namespace std; esto es bueno y malo al mismo tiempo porque por un lado si nosotros trabajamos con using como directiva, este traera todos los nombres almacenados en la libreria estandar, y si nosotros trabajamos con otras nombres de espacio en el encabezado se contaminara con todo esto pero si tenemos algo a favor, es el hecho de evitar tener que usar std:: en todos los metodos usados de la libreria estandar, como vinimos haciendo hasta ahora, en lugar de tener que usar la declaracion de using por cada funcion a utilizar.
En resumen, el namespace es utilizado para evitar posibles conflictos de nombres cuando estamos creando nuestro programa, porque mediante namespace podemos definir mejor la ubicacion de nuestras clases y/o funciones, tal como vimos hasta ahora, obviamente en programas cortos esto no tendra mucho sentido pero a programas mas largos y complejos donde indefectiblemente podemos llegar a repetir nombres nos puede generar problemas de compilacion o enlace y por ende dolores de cabeza, tambien aprendimos la verdadera funcion de utilizar el namespace std en nuestros programas, como generar alias de nuestros namespace y por ultimo como no utilizar toda la libreria sino algun metodo y/o variable especifica del namespace. Espero les haya sido util, nos vemos en el proximo post.

Anuncios