¿Por qué C ++ necesita un archivo de encabezado separado?

Nunca entendí realmente por qué C ++ necesita un archivo de encabezado separado con las mismas funciones que en el archivo .cpp. Hace que crear clases y refactorizarlas sea muy difícil y agrega archivos innecesarios al proyecto. Y luego está el problema de tener que incluir archivos de encabezado, pero tener que comprobar explícitamente si ya se ha incluido.

C ++ fue ratificado en 1998, entonces, ¿por qué está diseñado de esta manera? ¿Qué ventajas tiene tener un archivo de encabezado separado?


Siguiente pregunta:

¿Cómo encuentra el comstackdor el archivo .cpp con el código que contiene, cuando todo lo que incluyo es el archivo .h? ¿Asume que el archivo .cpp tiene el mismo nombre que el archivo .h o realmente revisa todos los archivos del árbol de directorios?

Parece que pregunta por separar las definiciones de las declaraciones, aunque hay otros usos para los archivos de encabezado.

La respuesta es que C ++ no “necesita” esto. Si marca todo en línea (que de todos modos es automático para funciones miembro definidas en una definición de clase), entonces no hay necesidad de la separación. Puede simplemente definir todo en los archivos de encabezado.

Las razones por las que puede querer separar son:

  1. Para mejorar los tiempos de construcción.
  2. Para vincular contra el código sin tener la fuente de las definiciones.
  3. Para evitar marcar todo “en línea”.

Si su pregunta más general es “¿por qué C ++ no es idéntico a Java?”, Entonces tengo que preguntar, “¿por qué estás escribiendo C ++ en lugar de Java?” ;-pag

Más en serio, sin embargo, la razón es que el comstackdor de C ++ no puede simplemente llegar a otra unidad de traducción y descubrir cómo usar sus símbolos, de la manera que javac puede y lo hace. El archivo de encabezado es necesario para declarar al comstackdor lo que puede esperar estar disponible en el momento del enlace.

Entonces #include es una sustitución textual directa. Si define todo en archivos de encabezado, el preprocesador termina creando una enorme copia y pegado de cada archivo fuente en su proyecto y alimentándolo en el comstackdor. El hecho de que el estándar C ++ fue ratificado en 1998 no tiene nada que ver con esto, es el hecho de que el entorno de comstackción para C ++ se basa tan estrechamente en el de C.

Convirtiendo mis comentarios para responder a su pregunta de seguimiento:

¿Cómo encuentra el comstackdor el archivo .cpp con el código en él?

No lo hace, al menos no en el momento en que comstack el código que usó el archivo de encabezado. Las funciones con las que está enlazando ni siquiera tienen que haber sido escritas aún, no importa si el comstackdor sabe en qué archivo .cpp estarán. Todo lo que el código de llamada necesita saber en tiempo de comstackción se expresa en la statement de la función. En el momento del enlace, proporcionará una lista de archivos .o , o bibliotecas estáticas o dinámicas, y el encabezado en vigencia es una promesa de que las definiciones de las funciones estarán ahí en alguna parte.

C ++ lo hace de esa manera porque C lo hizo de esa manera, entonces la verdadera pregunta es por qué C lo hizo de esa manera? Wikipedia habla un poco de esto.

Los lenguajes comstackdos más nuevos (como Java, C #) no usan declaraciones directas; los identificadores se reconocen automáticamente desde los archivos de origen y se leen directamente desde los símbolos de la biblioteca dinámica. Esto significa que los archivos de encabezado no son necesarios.

Algunas personas consideran que los archivos de encabezado son una ventaja:

  • Se afirma que habilita / impone / permite la separación de la interfaz y la implementación, pero por lo general, este no es el caso. Los archivos de encabezado están llenos de detalles de implementación (por ejemplo, las variables miembro de una clase deben especificarse en el encabezado, aunque no son parte de la interfaz pública) y las funciones pueden, y a menudo se definen, en línea en la statement de clase en el encabezado, otra vez destruyendo esta separación.
  • A veces se dice que mejora el tiempo de comstackción porque cada unidad de traducción se puede procesar de forma independiente. Y, sin embargo, C ++ es probablemente el lenguaje más lento que existe cuando se trata de tiempos de comstackción. Una parte del motivo es la gran cantidad de inclusiones repetidas del mismo encabezado. Una gran cantidad de encabezados están incluidos en varias unidades de traducción, lo que requiere que se analicen varias veces.

En última instancia, el sistema de encabezado es un artefacto de los años 70 cuando se diseñó C. En aquel entonces, las computadoras tenían muy poca memoria, y mantener todo el módulo en memoria simplemente no era una opción. Un comstackdor tuvo que comenzar a leer el archivo en la parte superior, y luego proceder linealmente a través del código fuente. El mecanismo de encabezado lo habilita. El comstackdor no tiene que considerar otras unidades de traducción, solo tiene que leer el código de arriba a abajo.

Y C ++ retuvo este sistema por compatibilidad con versiones anteriores.

Hoy, no tiene sentido. Es ineficiente, propenso a errores y excesivamente complicado. Hay formas mucho mejores de separar la interfaz y la implementación, si ese fuera el objective.

Sin embargo, una de las propuestas para C ++ 0x era agregar un sistema de módulos adecuado, permitiendo que el código se comstackra de manera similar a .NET o Java, en módulos más grandes, todo de una vez y sin encabezados. Esta propuesta no hizo el corte en C ++ 0x, pero creo que todavía está en la categoría “nos encantaría hacer esto más tarde”. Quizás en un TR2 o similar.

Para mi comprensión (limitada, normalmente no soy C desarrollador), esto está enraizado en C. Recuerde que C no sabe qué clases o espacios de nombres son, es solo un progtwig largo. Además, las funciones deben declararse antes de usarlas.

Por ejemplo, lo siguiente debería dar un error de comstackción:

 void SomeFunction() { SomeOtherFunction(); } void SomeOtherFunction() { printf("What?"); } 

El error debería ser que “SomeOtherFunction no está declarado” porque lo llamas antes de su statement. Una forma de arreglar esto es moviendo SomeOtherFunction arriba de SomeFunction. Otro enfoque es declarar primero la firma de funciones:

 void SomeOtherFunction(); void SomeFunction() { SomeOtherFunction(); } void SomeOtherFunction() { printf("What?"); } 

Esto le permite al comstackdor saber: busque en algún lugar del código, hay una función llamada SomeOtherFunction que devuelve vacío y no toma ningún parámetro. Entonces, si encuentra un código que intenta llamar a SomeOtherFunction, no entre en pánico y en su lugar búsquelo.

Ahora imagine que tiene SomeFunction y SomeOtherFunction en dos archivos .c diferentes. Luego debe #incluir “SomeOther.c” en Some.c. Ahora, agregue algunas funciones “privadas” a SomeOther.c. Como C no conoce las funciones privadas, esa función también estaría disponible en Some.c.

Aquí es donde entran los archivos .h: especifican todas las funciones (y variables) que desea ‘Exportar’ desde un archivo .c al que se puede acceder en otros archivos .c. De esta forma, ganas algo como un scope Público / Privado. Además, puede entregar este archivo .h a otras personas sin tener que compartir su código fuente. Los archivos .h también funcionan contra archivos .lib comstackdos.

Entonces, la razón principal es realmente por conveniencia, por la protección del código fuente y por tener un poco de desacoplamiento entre las partes de su aplicación.

Eso fue C sin embargo. C ++ introdujo Clases y modificadores privados / públicos, por lo que si bien aún podría preguntar si son necesarios, C ++ AFAIK aún requiere la statement de funciones antes de usarlos. Además, muchos Desarrolladores C ++ son o fueron C devleopers también y se hicieron cargo de sus conceptos y hábitos a C ++. ¿Por qué cambiar lo que no está roto?

Primera ventaja: si no tiene archivos de encabezado, debería incluir los archivos fuente en otros archivos fuente. Esto haría que los archivos incluidos se comstackran nuevamente cuando el archivo incluido cambie.

Segunda ventaja: permite compartir las interfaces sin compartir el código entre diferentes unidades (diferentes desarrolladores, equipos, empresas, etc.)

C ++ fue diseñado para agregar características modernas de lenguaje de progtwigción a la infraestructura C, sin cambiar innecesariamente nada sobre C que no era específicamente sobre el lenguaje en sí.

Sí, en este punto (10 años después del primer estándar de C ++ y 20 años después de que comenzó a crecer de manera importante en el uso) es fácil preguntarse por qué no tiene un sistema de módulos adecuado. Obviamente, cualquier nuevo lenguaje que se diseñe hoy no funcionaría como C ++. Pero ese no es el punto de C ++.

El objective de C ++ es ser evolutivo, una continuación suave de la práctica existente, solo agregando nuevas capacidades sin (muy a menudo) romper cosas que funcionen adecuadamente para su comunidad de usuarios.

Esto significa que hace algunas cosas más difíciles (especialmente para las personas que comienzan un nuevo proyecto), y algunas cosas más fáciles (especialmente para aquellos que mantienen el código existente) de lo que harían otros idiomas.

Entonces, en lugar de esperar que C ++ se convierta en C # (lo que sería inútil ya que tenemos C #), ¿por qué no elegir la herramienta adecuada para el trabajo? Yo mismo me esfuerzo por escribir fragmentos significativos de nuevas funcionalidades en un lenguaje moderno (uso C #), y tengo una gran cantidad de C ++ existente que guardo en C ++ porque no tendría valor real al volver a escribirlo. todas. Se integran muy bien de todos modos, por lo que es en gran medida indoloro.

La necesidad de archivos de encabezado es el resultado de las limitaciones que tiene el comstackdor para saber sobre la información de tipo para funciones y / o variables en otros módulos. El progtwig comstackdo o la biblioteca no incluye la información de tipo requerida por el comstackdor para vincularse a ningún objeto definido en otras unidades de comstackción.

Para compensar esta limitación, C y C ++ permiten declaraciones y estas declaraciones se pueden incluir en módulos que las utilizan con la ayuda de la directiva #include del preprocesador.

Los lenguajes como Java o C # por otro lado incluyen la información necesaria para el enlace en el resultado del comstackdor (archivo de clase o ensamblado). Por lo tanto, ya no es necesario mantener las declaraciones independientes para ser incluidas por los clientes de un módulo.

El motivo de que la información de enlace no se incluya en la salida del comstackdor es simple: no es necesario en el tiempo de ejecución (cualquier comprobación de tipo ocurre en tiempo de comstackción). Solo desperdiciaría espacio. Recuerde que C / C ++ viene de una época en la que el tamaño de un archivo ejecutable o biblioteca sí importaba bastante.

No necesita un archivo de encabezado separado con las mismas funciones que en main. Solo lo necesita si desarrolla una aplicación que usa múltiples archivos de código y si usa una función que no fue declarada previamente.

Es realmente un problema de scope.

C ++ fue ratificado en 1998, entonces, ¿por qué está diseñado de esta manera? ¿Qué ventajas tiene tener un archivo de encabezado separado?

En realidad, los archivos de encabezado se vuelven muy útiles cuando se examinan progtwigs por primera vez, revisar archivos de encabezado (usando solo un editor de texto) le brinda una visión general de la architecture del progtwig, a diferencia de otros idiomas donde debe usar herramientas sofisticadas para ver clases y sus funciones miembro

Creo que la razón real (histórica) detrás de los archivos de encabezado hacía que fuera más fácil para los desarrolladores de comstackdores … pero luego, los archivos de encabezado ofrecen ventajas.
Consulte esta publicación anterior para más discusiones …

Bueno, puedes desarrollar perfectamente C ++ sin archivos de encabezado. De hecho, algunas bibliotecas que usan plantillas de manera intensiva no usan el paradigma de encabezado / archivos de código (ver boost). Pero en C / C ++ no puede usar algo que no está declarado. Una manera práctica de lidiar con eso es usar archivos de cabecera. Además, obtiene la ventaja de compartir la interfaz sin compartir código / implementación. Y creo que no fue previsto por los creadores de C: cuando utilizas archivos de encabezado compartido, debes usar el famoso:

 #ifndef MY_HEADER_SWEET_GUARDIAN #define MY_HEADER_SWEET_GUARDIAN // [...] // my header // [...] #endif // MY_HEADER_SWEET_GUARDIAN 

eso no es realmente una característica del lenguaje, sino una forma práctica de lidiar con la inclusión múltiple.

Entonces, creo que cuando se creó C, los problemas con la statement directa se subestimaron y ahora cuando se usa un lenguaje de alto nivel como C ++ tenemos que lidiar con este tipo de cosas.

Otra carga para nosotros los usuarios pobres de C ++ …

Bueno, C ++ fue ratificado en 1998, pero se usó mucho más que eso, y la ratificación se basa principalmente en el uso actual en lugar de imponer la estructura. Y dado que C ++ se basó en C y C tiene archivos de encabezado, C ++ también los tiene.

La razón principal para los archivos de encabezado es habilitar la comstackción separada de archivos y minimizar las dependencias.

Digamos que tengo foo.cpp, y quiero usar el código de los archivos bar.h / bar.cpp.

Puedo # incluir “bar.h” en foo.cpp, y luego progtwigr y comstackr foo.cpp incluso si bar.cpp no ​​existe. El archivo de encabezado actúa como una promesa para el comstackdor de que las clases / funciones en bar.h existirán en tiempo de ejecución, y ya tiene todo lo que necesita saber.

Por supuesto, si las funciones en bar.h no tienen cuerpos cuando trato de vincular mi progtwig, entonces no se vincularán y obtendré un error.

Un efecto secundario es que puede proporcionar a los usuarios un archivo de encabezado sin revelar su código fuente.

Otra es que si cambia la implementación de su código en el archivo * .cpp, pero no cambia el encabezado, solo necesita comstackr el archivo * .cpp en lugar de todo lo que lo usa. Por supuesto, si aplica mucha implementación en el archivo de encabezado, esto se vuelve menos útil.

Si desea que el comstackdor descubra automáticamente los símbolos definidos en otros archivos, debe forzar al progtwigdor a colocar esos archivos en ubicaciones predefinidas (como la estructura de paquetes de Java determina la estructura de las carpetas del proyecto). Yo prefiero los archivos de encabezado. También necesitaría fonts de bibliotecas que use o alguna forma uniforme de colocar la información que necesita el comstackdor en binarios.