2.8 – Colisiones de nombres y una introducción a los espacios de nombres

Digamos que está conduciendo a la casa de un amigo por primera vez, y la dirección que se le dio es 245 Front Street en Mill City. Al llegar a Mill City, levantas tu mapa, solo para descubrir que Mill City en realidad tiene dos calles delanteras diferentes al otro lado de la ciudad. ¿A cuál irías? A menos que haya alguna pista adicional para ayudarlo a decidir (p. ej. recuerde que su casa está cerca del río) tendría que llamar a su amigo y pedirle más información. Debido a que esto sería confuso e ineficiente (particularmente para su cartero), en la mayoría de los países, todos los nombres de calles y direcciones de casas dentro de una ciudad deben ser únicos.

Del mismo modo, C++ requiere que todos los identificadores no sean ambiguos. Si se introducen dos identificadores idénticos en el mismo programa de una manera que el compilador o enlazador no pueda distinguirlos, el compilador o enlazador producirá un error. Este error se conoce generalmente como colisión de nombres (o conflicto de nombres).

Un ejemplo de una colisión de nombres

a.cpp:

1
2
3
4
5
6

#include <iostream>
void myFcn(int x)
{
std::cout << x;
}

principal.cpp:

1
2
3
4
5
6
7
8
9
10
11

#include <iostream>
void myFcn(int x)
{
std::cout << 2 * x;
}
int main()
{
volver 0;
}

Cuando el compilador compila este programa, se compilará a.cpp y la principal.cpp de forma independiente, y cada archivo se compilará sin problemas.

Sin embargo, cuando el enlazador se ejecute, enlazará todas las definiciones en a.cpp y main.cpp juntos, y descubrir definiciones conflictivas para la función myFcn. El enlazador abortará con un error. Tenga en cuenta que este error se produce a pesar de que myFcn nunca se llama!

La mayoría de las colisiones de nombres ocurren en dos casos:
1) Dos (o más) definiciones para una función (o variable global) se introducen en archivos separados que se compilan en el mismo programa. Esto dará lugar a un error de enlazador, como se muestra arriba.
2) Dos (o más) definiciones para una función (o variable global) se introducen en el mismo archivo (a menudo a través de un #include). Esto provocará un error en el compilador.

A medida que los programas se hacen más grandes y usan más identificadores, las probabilidades de que se introduzca una colisión de nombres aumentan significativamente. La buena noticia es que C++ proporciona muchos mecanismos para evitar colisiones de nombres. El ámbito local, que evita que las variables locales definidas dentro de las funciones entren en conflicto entre sí, es uno de esos mecanismos. Pero el ámbito local no funciona para los nombres de funciones. Entonces, ¿cómo evitamos que los nombres de las funciones entren en conflicto entre sí?

¿Qué es un espacio de nombres?

De vuelta a nuestra analogía de dirección por un momento, tener dos calles delanteras solo era problemático porque esas calles existían dentro de la misma ciudad. Por otro lado, si tuviera que entregar correo a dos direcciones, una en 209 Front Street en Mill City, y otra dirección en 417 Front Street en Jonesville, no habría confusión sobre dónde ir. Dicho de otra manera, las ciudades proporcionan agrupaciones que nos permiten desambiguar direcciones que de otro modo podrían entrar en conflicto entre sí. Los espacios de nombres actúan como las ciudades en esta analogía.

Un espacio de nombres es una región que le permite declarar nombres dentro de ella con el propósito de desambiguar. El espacio de nombres proporciona un ámbito (llamado ámbito de espacio de nombres) a los nombres declarados dentro de él, lo que simplemente significa que cualquier nombre declarado dentro del espacio de nombres no se confundirá con nombres idénticos en otros ámbitos.

Key insight

Un nombre declarado en un espacio de nombres no se confundirá con un nombre idéntico declarado en otro ámbito.

Dentro de un espacio de nombres, todos los nombres deben ser únicos, de lo contrario se producirá una colisión de nombres.

Los espacios de nombres a menudo se usan para agrupar identificadores relacionados en un proyecto grande para ayudar a garantizar que no colisionen inadvertidamente con otros identificadores. Por ejemplo, si coloca todas sus funciones matemáticas en un espacio de nombres llamado matemáticas, entonces sus funciones matemáticas no chocarán con funciones con nombres idénticos fuera del espacio de nombres matemático.

Hablaremos sobre cómo crear sus propios espacios de nombres en una lección futura.

El espacio de nombres global

En C++, cualquier nombre que no esté definido dentro de una clase, función o espacio de nombres se considera parte de un espacio de nombres definido implícitamente llamado espacio de nombres global (a veces también llamado ámbito global).

En el ejemplo de la parte superior de la lección, las funciones main () y ambas versiones de myFcn () se definen dentro del espacio de nombres global. La colisión de nombres encontrada en el ejemplo ocurre porque ambas versiones de myFcn () terminan dentro del espacio de nombres global, lo que viola la regla de que todos los nombres en el espacio de nombres deben ser únicos.

El espacio de nombres std

Cuando se diseñó originalmente C++, todos los identificadores de la biblioteca estándar de C++ (incluidos std::cin y std::cout) estaban disponibles para usarse sin el prefijo std:: (formaban parte del espacio de nombres global). Sin embargo, esto significaba que cualquier identificador de la biblioteca estándar podría entrar en conflicto con cualquier nombre que eligiera para sus propios identificadores (también definido en el espacio de nombres global). El código que estaba funcionando podría tener un conflicto de nombres de repente cuando #incluyas un archivo nuevo de la biblioteca estándar. O peor, los programas que compilarían bajo una versión de C++ podrían no compilarse bajo una versión futura de C++, ya que los nuevos identificadores introducidos en la biblioteca estándar podrían tener un conflicto de nombres con el código ya escrito. Así que C++ movió toda la funcionalidad de la biblioteca estándar a un espacio de nombres llamado » std » (abreviatura de standard).

Resulta que el nombre de std::cout no es realmente std:: cout. En realidad es solo cout, y std es el nombre del espacio de nombres del que forma parte el identificador cout. Como cout está definido en el espacio de nombres std, el nombre cout no entrará en conflicto con ningún objeto o función llamado cout que creemos en el espacio de nombres global.

De manera similar, al acceder a un identificador definido en un espacio de nombres (por ejemplo , std::cout), debe decirle al compilador que estamos buscando un identificador definido dentro del espacio de nombres (std).

Información clave

Cuando utiliza un identificador definido dentro de un espacio de nombres (como el espacio de nombres std), debe decirle al compilador que el identificador vive dentro del espacio de nombres.

Hay varias maneras diferentes de hacer esto.

Calificador de espacio de nombres explícito std::

La forma más sencilla de decirle al compilador que queremos usar cout desde el espacio de nombres std es usando explícitamente el prefijo std::. Por ejemplo:

1
2
3
4
5
6
7

#include <iostream>
int main()
{
std::cout << «Hola mundo!»; // cuando decimos cout, nos referimos al cout definido en el espacio de nombres std
return 0;
}

El símbolo:: es un operador llamado operador de resolución de alcance. El identificador a la izquierda del símbolo :: identifica el espacio de nombres en el que se encuentra el nombre a la derecha del símbolo::. Si no se proporciona ningún identificador a la izquierda del símbolo::, se asume el espacio de nombres global.

Así que cuando decimos std:: cout, estamos diciendo «el cout que vive en std de espacio de nombres».

Esta es la forma más segura de usar cout, porque no hay ambigüedad sobre a qué cout hacemos referencia (la del espacio de nombres std).

Práctica recomendada

Use prefijos de espacio de nombres explícitos para acceder a identificadores definidos en un espacio de nombres.

Uso de std de espacio de nombres (y por qué evitarlo)

Otra forma de acceder a los identificadores dentro de un espacio de nombres es usar una instrucción using. Aquí está nuestro programa original «Hola mundo» con una directiva de uso:

1
2
3
4
5
6
7
8
9

#include <iostream>
using namespace std; // esto es un uso de la directiva indica al compilador para comprobar el espacio de nombres std hora de resolver los identificadores con ningún prefijo
int main()
{
cout << «Hola mundo!»; // cout no tiene prefijo, por lo que el compilador comprobará si cout está definido localmente o en el espacio de nombres std
return 0;
}

Una directiva using le indica al compilador que compruebe un espacio de nombres especificado al intentar resolver un identificador que no tiene prefijo de espacio de nombres. Así que en el ejemplo anterior, cuando el compilador vaya a determinar qué identificador es cout, comprobará tanto localmente (donde no está definido) como en el espacio de nombres std (donde coincidirá con std::cout).

Muchos textos, tutoriales e incluso algunos compiladores recomiendan o usan una directiva de uso en la parte superior del programa. Sin embargo, si se usa de esta manera, es una mala práctica y muy desaconsejada.

Considere el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <iostream> // importaciones, la declaración de std::cout
using namespace std; // hace std::cout accesible como «cout»
int cout() // declara nuestro propio «cout» función
{
volver 5;
}
int main()
{
cout << «Hola, mundo!»; // Error de compilación! ¿Qué cout queremos aquí? ¿La del espacio de nombres std o la que definimos arriba?
volver 0;
}

El programa anterior no compila, porque el compilador ahora no puede decir si queremos la función cout que definimos, o el cout que está definido dentro del espacio de nombres std.

Al usar una directiva using de esta manera, cualquier identificador que definamos puede entrar en conflicto con cualquier identificador con nombre idéntico en el espacio de nombres std. Peor aún, si bien un nombre de identificador puede no entrar en conflicto en la actualidad, puede entrar en conflicto con nuevos identificadores agregados al espacio de nombres std en futuras revisiones de lenguaje. ¡Este fue el punto de mover todos los identificadores de la biblioteca estándar al espacio de nombres std en primer lugar!

Advertencia

Evite usar directivas (como usar espacio de nombres std;) en la parte superior de su programa. Violan la razón por la que se agregaron los espacios de nombres en primer lugar.

Hablaremos más sobre el uso de declaraciones (y cómo usarlas de manera responsable) en la lección 6.12 Using Uso de declaraciones.

2.9 — Introducción a la preprocesador

Índice

2.7 — los Programas con múltiples archivos de código

Deja una respuesta

Tu dirección de correo electrónico no será publicada.