¿Por qué debemos incluir los archivos .h
y .cpp
mientras podemos hacer que funcione únicamente al incluir el .cpp
archivo?
Por ejemplo: crear file.h
declaraciones que contengan, luego crear file.cpp
definiciones que contengan e incluir ambas en main.cpp
.
Alternativamente: crear una file.cpp
declaración / definiciones que contengan (sin prototipos) incluyéndolo en main.cpp
.
Ambos trabajan para mi. No puedo ver la diferencia. Tal vez sea útil tener una idea del proceso de compilación y vinculación.
Respuestas:
Si bien puede incluir
.cpp
archivos como mencionó, esta es una mala idea.Como mencionó, las declaraciones pertenecen a los archivos de encabezado. Estos no causan problemas cuando se incluyen en varias unidades de compilación porque no incluyen implementaciones. La inclusión de la definición de una función o miembro de clase varias veces normalmente causará un problema (pero no siempre) porque el enlazador se confundirá y arrojará un error.
Lo que debería suceder es que cada
.cpp
archivo incluya definiciones para un subconjunto del programa, como una clase, un grupo de funciones lógicamente organizado, variables estáticas globales (use con moderación si es que lo hace), etc.Cada unidad de compilación (
.cpp
archivo) incluye las declaraciones que necesita para compilar las definiciones que contiene. Realiza un seguimiento de las funciones y clases a las que hace referencia, pero no contiene, por lo que el vinculador puede resolverlas más tarde cuando combina el código objeto en un archivo ejecutable o biblioteca.Ejemplo
Foo.h
-> contiene declaración (interfaz) para la clase Foo.Foo.cpp
-> contiene definición (implementación) para la clase Foo.Main.cpp
-> contiene el método principal, el punto de entrada del programa. Este código crea una instancia de un Foo y lo usa.Ambos
Foo.cpp
yMain.cpp
necesitan incluirFoo.h
.Foo.cpp
lo necesita porque está definiendo el código que respalda la interfaz de la clase, por lo que necesita saber qué es esa interfaz.Main.cpp
lo necesita porque está creando un Foo e invocando su comportamiento, por lo que tiene que saber cuál es ese comportamiento, el tamaño de un Foo en la memoria y cómo encontrar sus funciones, etc., pero todavía no necesita la implementación real.El compilador generará
Foo.o
desde elFoo.cpp
cual contiene todo el código de clase Foo en forma compilada. También genera elMain.o
que incluye el método principal y referencias no resueltas a la clase Foo.Ahora viene el enlazador, que combina los dos archivos de objetos
Foo.o
yMain.o
en un archivo ejecutable. Ve las referencias de Foo sin resolver,Main.o
pero ve queFoo.o
contiene los símbolos necesarios, por lo que "conecta los puntos", por así decirlo. Una llamada de funciónMain.o
ahora está conectada a la ubicación real del código compilado para que, en tiempo de ejecución, el programa pueda saltar a la ubicación correcta.Si hubiera incluido el
Foo.cpp
archivoMain.cpp
, habría dos definiciones de la clase Foo. El enlazador vería esto y diría "No sé cuál elegir, así que esto es un error". El paso de compilación tendría éxito, pero la vinculación no. (A menos que simplemente no compile,Foo.cpp
pero ¿por qué está en un.cpp
archivo separado ?)Finalmente, la idea de diferentes tipos de archivos es irrelevante para un compilador C / C ++. Compila "archivos de texto" que, con suerte, contienen un código válido para el idioma deseado. A veces puede saber el idioma en función de la extensión del archivo. Por ejemplo, compile un
.c
archivo sin opciones de compilación y asumirá C, mientras que una extensión.cc
o.cpp
le indicará que asuma C ++. Sin embargo, puedo decirle fácilmente a un compilador que compile un archivo.h
o incluso.docx
como C ++, y emitirá un.o
archivo object ( ) si contiene un código C ++ válido en formato de texto sin formato. Estas extensiones son más para el beneficio del programador. Si veoFoo.h
yFoo.cpp
, inmediatamente asumo que el primero contiene la declaración de la clase y el segundo contiene la definición.fuente
Foo.cpp
enMain.cpp
que no es necesario incluir el.h
archivo, que tiene uno menos de archivos, todavía ganar en la división de código en archivos separados para facilitar la lectura, y su comando de compilación es más simple. Si bien entiendo la importancia de los archivos de encabezado, no creo que sea asíIf you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.
La oración más importante con respecto a la pregunta.Leer más sobre el papel de la C y preprocesador de C ++ , que es conceptualmente la primera "fase" de la C o C ++ compilador (históricamente era un programa separado
/lib/cpp
; ahora, por razones de rendimiento que se integra dentro del compilador adecuadocc1
occ1plus
). Lea en particular la documentación delcpp
preprocesador GNU . Entonces, en la práctica, el compilador conceptualmente procesa primero su unidad de compilación (o unidad de traducción ) y luego trabaja en el formulario preprocesado.Probablemente necesitará incluir siempre el archivo de encabezado
file.h
si contiene (según lo dictado por convenciones y hábitos ):typedef
. ej .struct
,class
etc., ...)static inline
funcionesTenga en cuenta que es una cuestión de convenciones (y conveniencia) ponerlas en un archivo de encabezado.
Por supuesto, su implementación
file.cpp
necesita todo lo anterior, por lo que quiere al#include "file.h"
principio.Esta es una convención (pero muy común). Puede evitar los archivos de encabezado y copiar y pegar su contenido en archivos de implementación (es decir, unidades de traducción). Pero no quiere eso (excepto quizás si su código C o C ++ se genera automáticamente; entonces podría hacer que el programa generador haga esa copia y pegue, imitando la función del preprocesador).
El punto es que el preprocesador está haciendo Pruebas únicas operaciones. Podría (en principio) evitarlo completamente copiando y pegando, o reemplazarlo por otro "preprocesador" o generador de código C (como gpp o m4 ).
Un problema adicional es que los estándares recientes de C (o C ++) definen varios encabezados estándar. La mayoría de las implementaciones realmente implementan estos encabezados estándar como archivos (específicos de implementación) , pero creo que sería posible que una implementación conforme implemente estándares incluidos (como
#include <stdio.h>
para C o#include <vector>
C ++) con algunos trucos de magia (por ejemplo, usando alguna base de datos o algunos información dentro del compilador).Si usa compiladores de GCC (por ejemplo,
gcc
og++
) puede usar el-H
indicador para informarse sobre cada inclusión y los-C -E
indicadores para obtener el formulario preprocesado. Por supuesto, hay muchos otros indicadores de compilación que afectan el preprocesamiento (por ejemplo,-I /some/dir/
para agregar/some/dir/
para buscar archivos incluidos y-D
para predefinir alguna macro de preprocesador, etc., etc.).NÓTESE BIEN. Las versiones futuras de C ++ (tal vez C ++ 20 , tal vez incluso más tarde) podrían tener módulos C ++ .
fuente
Debido al modelo de compilación de unidades múltiples de C ++, necesita una forma de tener código que aparece en su programa solo una vez (definiciones), y necesita una forma de tener código que aparece en cada unidad de traducción de su programa (declaraciones).
De esto nace el idioma de encabezado de C ++. Es una convención por una razón.
Usted puede volcar todo su programa en una sola unidad de traducción, pero esto trae consigo problemas con la reutilización de código, prueba de la unidad y la manipulación de dependencia entre módulos. También es solo un gran desastre.
fuente
La respuesta seleccionada de ¿Por qué necesitamos escribir un archivo de encabezado? es una explicación razonable, pero quería agregar detalles adicionales.
Parece que lo racional para los archivos de encabezado tiende a perderse en la enseñanza y la discusión de C / C ++.
El archivo de encabezados proporciona una solución a dos problemas de desarrollo de aplicaciones:
C / C ++ puede escalar desde programas pequeños hasta programas muy grandes de líneas multimillonarias y miles de archivos. El desarrollo de aplicaciones puede escalar de equipos de un desarrollador a cientos de desarrolladores.
Puedes usar varios sombreros como desarrollador. En particular, puede ser el usuario de una interfaz para funciones y clases, o puede ser el escritor de una interfaz de funciones y clases.
Cuando está utilizando una función, necesita conocer la interfaz de la función, qué parámetros usar, qué funciones devuelve y necesita saber qué hace la función. Esto se documenta fácilmente en el archivo de encabezado sin tener que mirar la implementación. ¿Cuándo has leído la implementación de
printf
? Comprar lo usamos todos los días.Cuando usted es el desarrollador de una interfaz, el sombrero cambia la otra dirección. El archivo de encabezado proporciona la declaración de la interfaz pública. El archivo de encabezado define lo que otra implementación necesita para usar esta interfaz. La información interna y privada de esta nueva interfaz no (y no debe) declararse en el archivo de encabezado. El archivo de encabezado público debe ser todo lo que alguien necesita para usar el módulo.
Para el desarrollo a gran escala, compilar y vincular puede llevar mucho tiempo. De muchos minutos a muchas horas (¡incluso a muchos días!). Dividir el software en interfaces (encabezados) e implementaciones (fuentes) proporciona un método para compilar solo archivos que necesitan compilarse en lugar de reconstruir todo.
Además, los archivos de encabezado permiten al desarrollador proporcionar una biblioteca (ya compilada) así como un archivo de encabezado. Es posible que otros usuarios de la biblioteca nunca vean la implementación adecuada, pero aún pueden usar la biblioteca con el archivo de encabezado. Hace esto todos los días con la biblioteca estándar C / C ++.
Incluso si está desarrollando una aplicación pequeña, el uso de técnicas de desarrollo de software a gran escala es un buen hábito. Pero también debemos recordar por qué usamos estos hábitos.
fuente
Es posible que también te preguntes por qué no solo colocas todo tu código en un solo archivo.
La respuesta más simple es el mantenimiento del código.
Hay momentos en que es razonable crear una clase:
El momento en que es razonable alinear totalmente en un encabezado es cuando la clase es realmente una estructura de datos con algunos captadores y definidores básicos y quizás un constructor que toma valores para inicializar sus miembros.
(Las plantillas, que deben estar todas en línea, son un problema ligeramente diferente).
El otro momento para crear una clase dentro de un encabezado es cuando puede usar la clase en múltiples proyectos y específicamente quiere evitar tener que vincular bibliotecas.
Un momento en el que puede incluir una clase completa dentro de una unidad de compilación y no exponer su encabezado es:
Una clase "impl" que solo usa la clase que implementa. Es un detalle de implementación de esa clase, y externamente no se usa.
Una implementación de una clase base abstracta que se crea mediante algún tipo de método de fábrica que devuelve un puntero / referencia / puntero inteligente) a la clase base. El método de fábrica estaría expuesto por la clase misma no lo estaría. (Además, si la clase tiene una instancia que se registra en una tabla a través de una instancia estática, ni siquiera necesita exponerse a través de una fábrica).
Una clase de tipo "functor".
En otras palabras, donde no desea que nadie incluya el encabezado.
Sé lo que podría estar pensando ... Si es solo por mantenimiento, al incluir el archivo cpp (o un encabezado totalmente integrado) puede editar fácilmente un archivo para "encontrar" el código y simplemente reconstruirlo.
Sin embargo, "mantenibilidad" no es solo tener el código ordenado. Es una cuestión de impactos del cambio. En general, se sabe que si mantiene un encabezado sin cambios y simplemente cambia un
(.cpp)
archivo de implementación , no será necesario reconstruir la otra fuente porque no debería haber efectos secundarios.Esto hace que sea "más seguro" realizar tales cambios sin preocuparse por el efecto de imitación y eso es lo que realmente significa "mantenibilidad".
fuente
El ejemplo de Snowman necesita una pequeña extensión para mostrar exactamente por qué se necesitan archivos .h.
Agregue otra barra de clase a la obra que también depende de la clase Foo.
Foo.h -> contiene una declaración para la clase Foo
Foo.cpp -> contiene la definición (impementación) de la clase Foo
Main.cpp -> utiliza variables del tipo Foo.
Bar.cpp -> también usa variables de tipo Foo.
Ahora todos los archivos cpp deben incluir Foo.h Sería un error incluir Foo.cpp en más de otro archivo cpp. Linker fallaría porque la clase Foo se definiría más de una vez.
fuente
.h
archivos de todos modos, con incluir guardias y es exactamente el mismo problema que tiene con los.h
archivos de todos modos. Esto no es para qué.h
sirven los archivos..cpp
archivo.Si escribe el código completo en el mismo archivo, su código será feo.
En segundo lugar, no podrás compartir tu clase escrita con otros. Según la ingeniería de software, debe escribir el código del cliente por separado. El cliente no debe saber cómo está funcionando su programa. Solo necesitan salida. Si escribe todo el programa en el mismo archivo, perderá la seguridad de su programa.
fuente