Espacios de nombres
Desde las primeras prácticas se creaba siempre un preámbulo, al comienzo de cada *.cpp, similar a lo siguiente:
#include <iostream>
using namespace std;
int main()
{
cout << "¡Hola, mundo!" << endl;
}
El motivo de la tercera línea, using namespace std, es el de dar acceso al espacio de nombres (namespace) std, donde se encuentra encerrada toda la librería estándar. El motivo de encerrar la librería estándar en un espacio de nombres no es otro que el de hacer más sencilla la creación de proyectos muy grandes, de manera que el proyecto no deje de compilar debido a que se han escogido los mismos nombres para dos funciones, clases, constantes o variables.
En caso de no usar using namespace, aún es necesario indicar de alguna manera en qué espacio de nombres residen los objetos, clases, etc. que se estén usando. Para ello, es posible emplear el operador de ámbito, "::". Por ejemplo:
#include <iostream>
int main()
{
std::cout << "¡Hola, mundo!" << std::endl;
}
De esta manera se da acceso selectivamente al espacio de nombres std, para todos y cada uno de los identificadores que se vayan a utilizar. Aunque en un primer momento pueda parecer muy trabajoso, es la mejor forma de evitar problemas de coincidencias de identificadores. Una técnica que se podría decir, se sitúa en un lugar intermedio, involucra de nuevo a la cláusula using. Por ejemplo:
#include <iostream>
using std::cout;
using std::endl;
int main()
{
cout << "¡Hola, mundo!" << std;
}
Los espacios de nombres no son sólo exclusivos de la librería estándar de C++. El programador puede crear sus propios espacios de nombres, usando la palabra clave namespace, poniendo a continuación el nombre del espacio de nombres, y encerrando entre llaves todos los miembros de ese espacio de nombres. Por ejemplo:
#include <iostream>
namespace Mates {
int sumar(int op1, int op2)
{
return op1 + op2;
}
}
int main()
{
std::cout << "¡Hola, mundo!" << std::endl;
std::cout << "3 y 2 son: " << Mates::sumar( 3, 2 ) << std::endl;
}
De nuevo, sería posible utilizar using
Mates::sumar y otros, en lugar de cualificar cada identificador por separado. Pero, como se comentaba más arriba, cualificar cada identificador es preferible, ya que al fin y al cabo esto hace que sea imposible que se produzcan errores de coincidencia de identificadores en distintos espacios de nombres (errores de ambigüedad). Por ejemplo, supóngase el siguiente
módulo:
// console.h
#ifndef CONSOLE_H_INCLUDED
#define CONSOLE_H_INCLUDED
#include <string>
#include <cstdio>
namespace Console {
class Output {
public:
Output(const std::string &fileName);
Output(FILE *file) : f( file )
{}
~Output()
{ std::fclose( f ); }
void write(const std::string &lin);
Output& operator<<(const std::string &l)
{ write( l ); return *this; }
private:
FILE * f;
};
extern Output cout;
}
#endif // CONSOLE_H_INCLUDED
La clase Output (salida) es capaz de volcar texto (string) en un archivo. Se indica, con la palabra clave extern, que se va a crear en otro archivo (un archivo *.cpp) el objeto cout de la clase Output. El archivo de implementación del módulo Console, console.cpp, es como sigue:
// console.cpp
#include <string>
#include "console.h"
Console::Output Console::cout(stdout);
Console::Output::Output(const std::string &fileName)
{
f = std::fopen( fileName.c_str(), "wt" );
}
void Console::Output::write(const std::string &lin)
{
std::fprintf( f, "%s\n", lin.c_str() );
}
En este archivo de implementación se crea el objeto cout de la clase Output, volcando toda la salida al identificador del archivo que se le pasa entre paréntesis al constructor, stdout. En realidad, stdout es un identificador especial que señala al archivo también especial de la salida estándar, lo que normalmente es la pantalla. Este módulo puede ser usado desde main.cpp, el archivo principal del proyecto.
#include <iostream>
#include "console.h"
int main()
{
std::cout << "Hello world!\n";
return 0;
}
En este momento, en la función main() se puede hacer referencia a dos objetos cout. Uno ha sido creado por la librería dentro del espacio de nombres std, mientras que el segundo fue creado por el programador en el espacio de nombres Console. Tal y como se muestra el ejemplo más arriba, en este momento el programa compila y funciona sin problemas, usando el objeto cout en el espacio de nombres std, es decir, std::cout. El programa funcionará de igual manera empleando el objeto cout en Console, es decir Console::cout, como se muestra a continuación.
#include <iostream>
#include "console.h"
int main()
{
Console::cout << "Hello world!\n";
return 0;
}
Sin embargo, el programa fallará irremisiblemente si se intentan abrir ambos espacios de nombres:
#include <iostream>
#include "console.h"
using namespace std;
using namespace Console;
int main()
{
cout << "Hello world!\n";
return 0;
}
El programa no compila, debido a que existe un error de ambigüedad cuando se desea utilizar cout.... ¿esa referencia a cout, es en realidad una referencia a std::cout, o a Console::cout? El compilador no puede saberlo, así que detiene la compilación.
En conclusión, los espacios de nombres son una excelente herramienta para el programador, permitiéndole compartimentar diferentes partes de un proyecto, evitando que se produzcan coincidencias de identificadores entre esas partes (especialmente cuando las partes han sido creadas por diferentes programadores). La directiva de compilación using namespace es un recurso a evitar, pues puede provocar errores de ambigüedad a la hora de compilar. La solución más efectiva es cualificar cada identificador con el espacio de nombres al que pertenece, mediante el uso del operador de ámbito.