según la documentación cada vez que #incluyo una cabecera todo su contenido se añaden al espacio de nombres std
Esa parte la estás entendiendo mal
Cuando tu estás escribiendo una parte del programa eres libre de elegir el espacio de nombres en el que se va a alojar dicho código. De hecho podrías decidir no incluirlo en ningún espacio de nombres:
void func()
{ std::cout << "func()"; }
namespace POO
{
void func()
{ std::cout << "POO:func()"; }
}
int main()
{
func();
POO::func();
}
Ahora vamos a aparcar un momento este tema y vamos a hablar del proceso de compilación en C.
Para compilar un programa en C se ejecutan 3 tareas independientes:
- Preprocesador
- Compilación
- Enlazado
El preprocesador es un programa que lee los diferentes archivos de nuestro programa en busca de directivas del preprocesador (esas instrucciones que empiezan con #). Cada vez que localiza alguna, reemplaza dicha directiva por otro contenido. Así, por ejemplo:
#include
se reemplaza por el contenido del archivo al que hace referencia
#define
crea una macro, la directiva desaparece
#ifdef
, #else
, #endif
, ... son directivas de control. El código que está entre ellas puede desaparecer del programa final si no se cumplen las condiciones
El preprocesador no solo busca directivas como las mencionadas, también busca las macros que se han definido y las reemplaza por el código de la propia macro.
Este proceso es recursivo, es decir, cada vez que se hace un #include
, el contenido del archivo incluido se pasa también por el preprocesador. El resultado final es un archivo generalmente grande que no contiene ninguna directiva de preprocesador ni ninguna macro, solo código C en estado puro.
Este código resultante es el que se pasa a la siguiente etapa, la etapa de compilación. En esta etapa se leen los diferentes archivos y se genera código objeto.
Finalmente le llega el turno al enlazador. El enlazador coge todos los códigos objeto y los une bajo un mismo ejecutable/librería.
Para hacernos una idea gráfica, el código objeto lo puedes equiparar a los retales que se usan para crear una funda. El enlazador sería el proceso de coger todos esos retales y unirlos para crear el producto final.
Vamos a verlo con un ejemplo:
A.h
#ifndef A_H
#define A_H
#include "B.h"
int funcA(int value)
{ return value * 2 - 1; }
#endif
B.h
#ifndef B_H
#define B_H
#include "A.h"
int funcB(int value)
{ return funcA(value + 10); }
#endif
main.c
#include "B.h"
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
Al compilar main.c
sucede lo siguiente:
Precompilador
encuentra #include "b.h"
, por lo que reemplaza esta directiva por el contenido del archivo
#ifndef B_H
#define B_H
#include "A.h"
int funcB(int value)
{ return funcA(value + 10); }
#endif
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
Se procesa `#ifdef B_H. Como el síbolo no está definido, la guarda simplemente desaparece
#define B_H
#include "A.h"
int funcB(int value)
{ return funcA(value + 10); }
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
Se procesaría #define B_H
, este lo voy a dejar para más adelante por claridad
Se procesa #include "A.h"
#define B_H
#ifndef A_H
#define A_H
#include "B.h"
int funcA(int value)
{ return value * 2 - 1; }
#endif
int funcB(int value)
{ return funcA(value + 10); }
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
Se procesa #ifdef A_H
. El símbolo no está definido así que la guarda desaparece
#define B_H
#define A_H
#include "B.h"
int funcA(int value)
{ return value * 2 - 1; }
int funcB(int value)
{ return funcA(value + 10); }
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
Se procesa `#include "B.h"
#define B_H
#define A_H
#ifndef B_H
#define B_H
#include "A.h"
int funcB(int value)
{ return funcA(value + 10); }
#endif
int funcA(int value)
{ return value * 2 - 1; }
int funcB(int value)
{ return funcA(value + 10); }
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
Se procesa #ifdef B_H
. Como se puede ver, este símbolo ya está definido en la primera línea. El resultado es que todo lo que se encuentre entre #ifdef
y su correspondiente #endif
desaparece
#define B_H
#define A_H
int funcA(int value)
{ return value * 2 - 1; }
int funcB(int value)
{ return funcA(value + 10); }
#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }
P R O G R A M
El preprocesador se apuntaría las diferentes macros y el código que las reemplaza
Finalmente, el procesador encuentra usos de las distintas macros y reemplaza dichos usos por el código correspondiente
int funcA(int value)
{ return value * 2 - 1; }
int funcB(int value)
{ return funcA(value + 10); }
int main() { int var = funcB(123); std::cout << var; }
//~~~~~~~~ ~~~~~~~~~~~~~ ~ ~
// ~ ~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
// P R O G R A M
El procesador ha terminado su tarea, ahora le llegaría el turno al compilador.
Puedes ver el ejemplo funcionando aquí
Como puedes ver, pese a que hemos hecho uso de #include
, el código no ha acabado en el espacio de nombres std
. Eso solo sucederá si el código, en el momento de escribir la cabecera, se ha incluído en ese espacio de nombres o si, por conveniencia, lo hacemos nosotros al invocar el #include
. Así, podemos modificar facilmente nuestro main.c
:
namespace POO
{
#include "B.h"
}
int main()
{
int var = POO::funcB(123);
std::cout << var;
}
Puedes ver el ejemplo funcionando aquí
Este tipo de prácticas no son especialmente recomendables, pero es algo que, como puedes ver, es posible realizarlas.
Así pues, recapitulando, las funciones estarán, por normal general, bajo el espacio de nombres que haya decidido el autor de dicha librería. Los espacios de nombres no hacen magia y no aparecen y desaparecen a placer.
Ahora, hablando brevemente de la librería estándar:
La parte que viene heredada de C se encuentra disponible a través de dos librerías distintas, la que acaba en .h y la que empieza por c:
#include <stdio.h>
#include <cstdio>
La versión que empieza por c es la que se debería usar, ya que está ligeramente más adaptada a los requisitos propios de C (uso de espacio de nombres, tipado fuerte en algunos casos, ...)
La librería estándar propia de C++ se encuentra al completo bajo el espacio de nombres std
.
Así que, como puedes ver, al usar funciones heredadas de C puedes encontrarte con ambos casos, que estén bajo std
o que sean funciones libres. Todo depende de cómo las incluyas en tu código.