Pregunta

¿Por qué C ++ tiene archivos de encabezado y archivos .cpp?

¿Fue útil?

Solución

Bueno, la razón principal sería separar la interfaz de la implementación. El encabezado declara " what " una clase (o lo que sea que se esté implementando) funcionará, mientras que el archivo cpp define " cómo " realizará esas funciones.

Esto reduce las dependencias, por lo que el código que usa el encabezado no necesariamente necesita conocer todos los detalles de la implementación y cualquier otra clase / encabezado necesario solo para eso. Esto reducirá los tiempos de compilación y también la cantidad de compilación necesaria cuando algo en la implementación cambia.

No es perfecto, y generalmente recurriría a técnicas como el Pimpl Idiom para separar correctamente la interfaz y la implementación, pero es un buen comienzo.

Otros consejos

compilación C ++

Una compilación en C ++ se realiza en 2 fases principales:

  1. El primero es la compilación de " fuente " archivos de texto en binario "objeto" archivos: el archivo CPP es el archivo compilado y se compila sin ningún conocimiento acerca de los otros archivos CPP (o incluso bibliotecas), a menos que se lo alimente a través de una declaración sin formato o inclusión de encabezado. El archivo CPP generalmente se compila en un .OBJ o un .O " objeto " archivo.

  2. El segundo es la vinculación de todos los '' objetos '' archivos y, por lo tanto, la creación del archivo binario final (ya sea una biblioteca o un ejecutable).

¿Dónde encaja el HPP en todo este proceso?

Un archivo CPP solitario y pobre ...

La compilación de cada archivo CPP es independiente de todos los demás archivos CPP, lo que significa que si A.CPP necesita un símbolo definido en B.CPP, como:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

No se compilará porque A.CPP no tiene forma de saber "hacer algo más". existe ... A menos que haya una declaración en A.CPP, como:

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

Luego, si tiene C.CPP que usa el mismo símbolo, copie / pegue la declaración ...

¡COPIAR / PEGAR ALERTA!

Sí, hay un problema. Las copias / pastas son peligrosas y difíciles de mantener. Lo que significa que sería genial si tuviéramos alguna forma de NO copiar / pegar, y aún así declarar el símbolo ... ¿Cómo podemos hacerlo? Mediante la inclusión de algún archivo de texto, que comúnmente tiene el sufijo .h, .hxx, .h ++ o, mi preferido para archivos C ++, .hpp:

// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // Etc.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse() ; // Defined in B.CPP
}

¿Cómo funciona include ?

Incluir un archivo, en esencia, analizará y luego copiará y pegará su contenido en el archivo CPP.

Por ejemplo, en el siguiente código, con el encabezado A.HPP:

// A.HPP
void someFunction();
void someOtherFunction();

... la fuente B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... se convertirá después de la inclusión:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

Una pequeña cosa: ¿por qué incluir B.HPP en B.CPP?

En el caso actual, esto no es necesario, y B.HPP tiene la declaración de función doSomethingElse , y B.CPP tiene la definición de función doSomethingElse (que es, por sí mismo una declaración). Pero en un caso más general, donde B.HPP se usa para declaraciones (y código en línea), podría no haber una definición correspondiente (por ejemplo, enumeraciones, estructuras simples, etc.), por lo que la inclusión podría ser necesaria si B.CPP usa esas declaraciones de B.HPP. Con todo, es "buen gusto". para que una fuente incluya de forma predeterminada su encabezado.

Conclusión

Por lo tanto, el archivo de encabezado es necesario, ya que el compilador de C ++ no puede buscar declaraciones de símbolos por sí solo y, por lo tanto, debe ayudarlo al incluir esas declaraciones.

Una última palabra: debe colocar protectores de encabezado alrededor del contenido de sus archivos HPP, para asegurarse de que las inclusiones múltiples no rompan nada, pero en general, creo que la razón principal de la existencia de los archivos HPP se explica más arriba.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

Debido a que C, donde se originó el concepto, tiene 30 años, y en aquel entonces, era la única forma viable de vincular el código de múltiples archivos.

Hoy, es un truco horrible que destruye totalmente el tiempo de compilación en C ++, causa innumerables dependencias innecesarias (porque las definiciones de clase en un archivo de encabezado exponen demasiada información sobre la implementación), y así sucesivamente.

Debido a que en C ++, el código ejecutable final no lleva ninguna información de símbolo, es un código de máquina más o menos puro.

Por lo tanto, necesita una manera de describir la interfaz de un fragmento de código, que es independiente del código en sí. Esta descripción está en el archivo de encabezado.

Porque C ++ los heredó de C. Lamentablemente.

Porque las personas que diseñaron el formato de la biblioteca no querían "desperdiciar" espacio para información raramente utilizada como macros de preprocesador C y declaraciones de funciones.

Dado que necesita esa información para indicarle a su compilador "esta función está disponible más tarde cuando el vinculador está haciendo su trabajo", tuvieron que encontrar un segundo archivo donde se pudiera almacenar esta información compartida.

La mayoría de los lenguajes después de C / C ++ almacenan esta información en la salida (código de bytes de Java, por ejemplo) o no utilizan un formato precompilado en absoluto, siempre se distribuyen en forma de origen y se compilan sobre la marcha (Python, Perl) .

Es la forma preprocesadora de declarar interfaces. Pones la interfaz (declaraciones de método) en el archivo de encabezado y la implementación en el cpp. Las aplicaciones que usan su biblioteca solo necesitan conocer la interfaz, a la que pueden acceder a través de #include.

A menudo querrá tener una definición de interfaz sin tener que enviar el código completo. Por ejemplo, si tiene una biblioteca compartida, enviaría un archivo de encabezado que define todas las funciones y símbolos utilizados en la biblioteca compartida. Sin archivos de encabezado, necesitaría enviar la fuente.

Dentro de un solo proyecto, los archivos de encabezado se utilizan, en mi humilde opinión, para al menos dos propósitos:

  • Claridad, es decir, al mantener las interfaces separadas de la implementación, es más fácil leer el código
  • Tiempo de compilación. Al usar solo la interfaz donde sea posible, en lugar de la implementación completa, el tiempo de compilación se puede reducir porque el compilador simplemente puede hacer una referencia a la interfaz en lugar de tener que analizar el código real (que, idealmente, solo tendría que hacerse una sola vez).

Respondiendo a Respuesta de MadKeithV ,

  

Esto reduce las dependencias para que el código que usa el encabezado no   necesariamente necesita conocer todos los detalles de la implementación y cualquier   otras clases / encabezados necesarios solo para eso. Esto reducirá   tiempos de compilación, y también la cantidad de compilación necesaria cuando   algo en la implementación cambia.

Otra razón es que un encabezado proporciona una identificación única para cada clase.

Entonces, si tenemos algo como

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

Tendremos errores cuando intentemos construir el proyecto, ya que A es parte de B, con encabezados evitaríamos este tipo de dolor de cabeza ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top