2009-06-16 5 views
46

¿Hay alguna manera de no tener que escribir declaraciones de funciones dos veces (encabezados) y aún conservar la misma escalabilidad en la compilación, la claridad en la depuración y la flexibilidad en el diseño cuando se programa en C++?¿Puedo escribir código en C++ sin encabezados (declaraciones de funciones repetitivas)?

+3

@nOrd ... o módulos ([n2073] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2073.pdf)) serán finalmente aceptados en el lenguaje –

Respuesta

9

No hay forma práctica de evitar encabezados. Lo único que podrías hacer es poner todo el código en un gran archivo de C++. Eso terminará en un lío inmanejable, así que por favor no lo hagas.

Por el momento, los archivos de encabezado C++ son un mal necesario. No me gustan, pero no hay forma de evitarlos. Sin embargo, me encantaría ver algunas mejoras e ideas frescas sobre el problema.

Btw - una vez que te has acostumbrado ya no es que ya está mal ... C++ (y cualquier otro idioma también) tiene cosas más molestas.

+0

En realidad, su solución "un gran archivo C++" podría descomponerse un poco usando '# include's. Estos no tendrían que ser "encabezados" para los archivos fuente compilados por separado (para que pueda nombrarlos .cpp para evitar/facilitar la confusión). Su maldad en mi humilde opinión, pero lo he visto hecho. –

5

Tiene que escribir la función declarar dos veces, en realidad (una vez en el archivo de encabezado, una vez en el archivo de implementación). La definición (implementación AKA) de la función se escribirá una vez, en el archivo de implementación.

Puede escribir todo el código en archivos de encabezado (en realidad es una práctica muy utilizada en programación genérica en C++), pero esto implica que cada archivo C/CPP incluido ese encabezado implicará la recompilación de la implementación de esos archivos de encabezado .

Si está pensando en un sistema similar a C# o Java, no es posible en C++.

+0

"esto implica que cada archivo C/CPP que incluye ese encabezado implicará la recompilación de la implementación de esos archivos de encabezado". Que es un problema menor si en realidad todo su código está en los encabezados, ya que presumiblemente solo tendrá un archivo cpp para compilar. Así que tendrás una gran compilación, pero al menos solo será una. El proyecto típico de C++ en el infierno del encabezado tiene muchos archivos cpp, cada uno de los cuales compila la mayoría o la totalidad del código del encabezado, para obtener más trabajo en total. –

+0

Ok. En principio, tienes razón. Pero, si tiene cientos o miles de unidades de traducción, entonces intentar convertirlas en una unidad de traducción (mediante la inclusión de archivos) será una pesadilla. Nunca lo intentaría de esta manera. –

+0

gracias, corrigió la confusión con declaración/definición. – thewreck

1

Por lo que yo sé, no. Los encabezados son una parte inherente de C++ como un lenguaje. No olvide que la declaración directa le permite al compilador simplemente incluir un puntero de función a un objeto/función compilado sin tener que incluir la función completa (que puede evitar declarando una función en línea (si el compilador así lo desea).

Si realmente, realmente, realmente odias hacer cabeceras, escribir un script en perl para autogenerar ellos, en su lugar. no estoy seguro de que lo recomiendo sin embargo.

3

en realidad ... se puede escribir la totalidad implementación en un archivo. Las clases con plantilla están todas definidas en el archivo de cabecera sin archivo cpp.

También puede guardar con las extensiones que desee. Luego, en las instrucciones #include, agregaría lude su archivo.

/* mycode.cpp */ 
#pragma once 
#include <iostreams.h> 

class myclass { 
public: 
    myclass(); 

    dothing(); 
}; 

myclass::myclass() { } 
myclass::dothing() 
{ 
    // code 
} 

Luego, en otro archivo

/* myothercode.cpp */ 
#pragma once 
#include "mycode.cpp" 

int main() { 
    myclass A; 
    A.dothing(); 
    return 0; 
} 

Es posible que tenga que configurar algunas reglas de generación, pero debería funcionar.

+1

Tengo que agregar ... La regla más importante para la codificación es facilitarle a los demás la lectura. Entonces la gente de C++ no sabría qué demonios está pasando. Esto no se recomienda, pero es posible;) – Kieveli

+0

Además, el OP preguntó sobre la escalabilidad de la compilación, lo que definitivamente tendría un impacto. –

+0

#incluyendo un archivo .cpp definitivamente obtendrá los programadores de mantenimiento en su caso (En una mala forma). –

0

Puede diseñar cuidadosamente sus funciones para que todas las funciones dependientes se compilen después de sus dependencias, pero como implicó Nils, eso no es práctico.

Catalin (perdone las marcas diacríticas que faltan) también sugirió una alternativa más práctica de definir sus métodos en los archivos de encabezado. En realidad, esto puede funcionar en la mayoría de los casos ... especialmente si tiene guardias en los archivos de encabezado para asegurarse de que solo se incluyan una vez.

Personalmente, creo que los archivos de cabecera + funciones de declaración es mucho más deseable para 'dar con la cabeza' el nuevo código, pero esa es una preferencia personal, supongo ...

8

Lo que he visto que algunas personas como tú es write everything in the headers. Eso le da a su propiedad deseada de tener que escribir los perfiles del método una sola vez.

Personalmente, creo que hay muy buenas razones por las cuales es mejor separar la declaración y la definición, pero si esto te angustia, hay una manera de hacer lo que quieras.

25

Lo sentimos, pero no existe la "mejor práctica" para eliminar encabezados en C++: es una mala idea, punto. Si los odias tanto, tienes tres opciones:

  • Familiarízate con las partes internas de C++ y cualquier compilador que estés utilizando; te encontrarás con problemas diferentes a los del desarrollador promedio de C++, y probablemente tendrás que resolverlos sin mucha ayuda.
  • Escoja un idioma que pueda usar "correcto" sin deprimirse
  • Obtenga una herramienta para generarlos para usted; usted todavía tiene cabeceras, pero a ahorrar un poco de esfuerzo a escribir
+1

-1 La herramienta lzz mencionada en una respuesta resuelve los problemas implicados por el "asker" sin los efectos negativos que describió (ya que la herramienta lzz usa encabezados. Simplemente no tiene que escribirlos). Eso hace que esta respuesta no sea constructiva. sry. – 0scar

+6

Punto justo. Confirmaré la tercera opción, gracias por explicar el error. – ojrac

+3

Creo que probablemente fue downvoted porque no era específico (¿cuáles son los "problemas infernales", por ejemplo) y por lo tanto inútil. Además, como anotó, es una opción y, por lo tanto, subjetiva, que generalmente no es útil en esta comunidad. – weberc2

9

En su artículo Simple Support for Design by Contract in C++, Pedro Guerreiro declarado:

Por lo general, una clase de C++ está disponible en dos archivos: el archivo de cabecera y la archivo de definición. ¿Dónde deberíamos escribir las aserciones: en el archivo de encabezado, porque las aserciones son especificación? O en el archivo de definición, ya que son ejecutables? O en ambos, ejecutando el riesgo de inconsistencia (y duplicando el trabajo)? Recomendamos, en cambio, que abandonamos el estilo tradicional , y acabar con el archivo de definición, usando sólo el archivo de cabecera , como si todas las funciones se definen en línea, muy parecido a Java y no Eiffel.

Esta es una cambio tan drástico de la normalidad de C++ que riesgos matar al esfuerzo en el principio. Por otro lado, el mantenimiento de dos archivos para cada clase es tan torpe, que tarde o temprano un entorno de desarrollo C++ se van a plantear que esconde eso de nosotros, nosotros que permite concentrarse en nuestras clases, sin tener que preocuparse sobre dónde están almacenados .

Eso fue en 2001. Estuve de acuerdo. Ahora es 2009 y aún no ha surgido "un entorno de desarrollo que lo oculte, lo que nos permite concentrarnos en nuestras clases". En cambio, los tiempos de compilación largos son la norma.

Afortunadamente ahora tenemos C#, donde podemos disfrutar no hay archivos de cabecera ... y mucho los tiempos de compilación :)


Nota: El enlace anterior parece haber muerto.Esta es la referencia completa a la publicación, tal como aparece en la sección Publications de website del autor:

Pedro Guerreiro, Soporte simple para Diseño por contrato en C++, HERRAMIENTAS USA 2001, Proceedings, páginas 24-34, IEEE, 2001.

+8

En mi experiencia C# compila más rápido que C++, y la comprobación de la dependencia (en VS2008 al menos) es mucho mejor. –

+0

Hay muchos idiomas que no requieren dolor de C++. Ve a ser uno de mis favoritos personales. – weberc2

+1

@MarkLakata - El problema no es cuál compila todas las fuentes en el sistema más rápido. El problema es que si edito los detalles de una implementación de clase, y cada clase en un programa grande depende de esa clase, con C++ solo tengo que recompilar un archivo .cpp y volver a vincular, donde con un sistema sin separación lo haría presumiblemente tiene que recompilar * todo *. –

36

Sentí lo mismo cuando comencé a escribir C, así que también investigué esto. La respuesta es que sí, es posible y no, no quieres.

Primero con el sí.

En GCC, usted puede hacer esto:

// foo.cph 

void foo(); 

#if __INCLUDE_LEVEL__ == 0 
void foo() { 
    printf("Hello World!\n"); 
} 
#endif 

Esto tiene el efecto deseado: que combinan tanto la cabecera y la fuente en un archivo que puede tanto ser incluidos y unidos.

Luego, con el Nº:

Esto sólo funciona si el compilador tiene acceso a toda la fuente. No puede usar este truco al escribir una biblioteca que desea distribuir, pero sí mantener el código cerrado. O bien distribuye el archivo .cph completo, o tiene que escribir un archivo .h por separado para ir con su .lib. Aunque tal vez podría autogenerarlo con el macroprocesador. Sin embargo, se pondría peludo.

Y razón # 2 por la que no quiere esto, y esa es probablemente la mejor: velocidad de compilación. Normalmente, los archivos de fuentes C solo deben recompilarse cuando el archivo cambia, o cambia cualquiera de los archivos que incluye.

  • El archivo C puede cambiar con frecuencia, pero el cambio solo implica volver a compilar el archivo que ha cambiado.
  • Los archivos de encabezado definen las interfaces, por lo que no deben cambiar con tanta frecuencia. Sin embargo, cuando lo hacen, desencadenan una recompilación de cada archivo fuente que los incluye.

Cuando todos sus archivos se combinan en los archivos de encabezado y fuente, cada cambio desencadenará una recompilación de todos los archivos de origen. C++ no es conocido por sus rápidos tiempos de compilación incluso ahora, imagine lo que sucedería cuando todo el proyecto tuviera que ser recompilado cada vez. Luego, extrapole eso a un proyecto de cientos de archivos fuente con dependencias complicadas ...

+3

Los archivos de encabezado funcionan muy bien en C, estoy de acuerdo con eso. Pero en C++ no siempre tienen sentido. Por ejemplo, no tiene mucho sentido declarar métodos privados en una declaración de clase. Debería poder definir tantos métodos privados como desee sin afectar los archivos externos. – Zelenova

1

Puede hacerlo sin encabezados. Pero, ¿por qué gastar esfuerzos tratando de evitar cuidadosamente las mejores prácticas desarrolladas durante muchos años por expertos?

Cuando escribí básico, me gustaron bastante los números de línea. Pero, no pensaría en tratar de meterlos en C++, porque esa no es la forma C++. Lo mismo aplica para los encabezados ... y estoy seguro de que otras respuestas explican todo el razonamiento.

0

Para práctico fines no, no es posible. Técnicamente, sí, puedes. Pero, francamente, es un abuso del idioma, y ​​debe adaptarse al idioma. O muévete a algo como C#.

5

Hay software de generación de archivos de cabecera. Nunca lo he usado, pero podría valer la pena investigarlo. Por ejemplo, echa un vistazo a mkhdr! Supuestamente escanea los archivos C y C++ y genera los archivos de encabezado apropiados.

(Sin embargo, como señala Richard, esto parece que limitar el uso de ciertas funciones de C++. Véase la respuesta de Richard lugar here right in this thread.)

+0

He estado usando makeheaders por algunos años. Por ahora no puedo escribir código C sin él; es mucho, mucho mejor que escribir archivos de encabezado y es una solución muy simple. Sin embargo, tiene un error que rompe algunas cadenas de dependencia; Puedo solucionarlo yo mismo algún día. No estoy seguro de si realmente funciona para plantillas o no, ya que lo uso para C. –

54

Uso Lzz. Toma un solo archivo y crea automáticamente .h y .cpp para usted con todas las declaraciones/definiciones en el lugar correcto.

Lzz es realmente muy poderoso, y maneja el 99% del total sintaxis de C++, incluyendo plantillas, especializaciones, etc, etc, etc.

actualización 150120:

reciente C++ '11 sintaxis/14 sólo puede ser utilizado dentro de los cuerpos de la función Lzz.

+1

+1: En realidad, lzz está diseñado como * bueno *: como un idioma fuente que produce C++. –

2

Usted puede evitar cabeceras. Completamente. Pero no lo recomiendo

Te enfrentarás con algunas limitaciones muy específicas. Una de ellas es que no podrá tener referencias circulares (no podrá tener la clase Parent con un puntero a una instancia de la clase ChildNode, y la clase ChildNode también contiene un puntero a una instancia de la clase Parent. 'Tendría que ser una o la otra.)

Hay otras limitaciones que acaba de terminar de hacer su código realmente extraño. Quédate con los encabezados Aprenderá a simpatizar con ellos (ya que ofrecen una buena y rápida sinopsis de lo que una clase puede hacer).

+0

Los "archivos de encabezado" son básicamente un truco de preprocesador. Puede hacer referencias directas en C++ sin ellas. –

+0

Pero son un truco del preprocesador_necesario: no podría usar referencias circulares correctamente sin ellos (problemas con tipos incompletos). C habría sido deficiente y el estándar probablemente hubiera sido cambiado. – bobobobo

+0

No, no lo son. Puede utilizar las referencias de clases de reenvío directamente en un solo archivo .cpp sin involucrar al preprocesador. –

0

Es una buena práctica usar los archivos de encabezado, y después de un tiempo crecerá en ti. Estoy de acuerdo en que tener solo un archivo es más fácil, pero también puede ser incorrecto.

algunas de estas cosas, althoug sentirse incómodo, le permiten obtener más de lo que parece.

como ejemplo pensar en punteros, el paso de parámetros por valor/referencia ... etc

para mí permitir que los archivos de cabecera-mí mantener mis proyectos adecuadamente estructurada

2

para ofrecer una variante de la respuesta popular de rix0rrr:

// foo.cph 

#define INCLUDEMODE 
#include "foo.cph" 
#include "other.cph" 
#undef INCLUDEMODE 

void foo() 
#if !defined(INCLUDEMODE) 
{ 
    printf("Hello World!\n"); 
} 
#else 
; 
#endif 

void bar() 
#if !defined(INCLUDEMODE) 
{ 
    foo(); 
} 
#else 
; 
#endif 

no recomiendo esto, poco creo que esta construcción demuestra la eliminación de la repetición de contenido a costa de la repetición de memoria. ¿Supongo que hace más fácil la copia de pasta? Eso no es realmente una virtud.

Al igual que con todos los otros trucos de esta naturaleza, una modificación en el cuerpo de una función todavía requerirá recopilación de todos los archivos incluidos en el archivo que contiene esa función. Las herramientas automáticas muy cuidadosas pueden evitar esto en parte, pero aún tendrían que analizar el archivo de origen para verificarlo, y deben construirse cuidadosamente para no reescribir su resultado si no es diferente.

Para otros lectores: yo pasamos unos minutos tratando de averiguar incluir guardias en este formato, pero no llegar a nada bueno. ¿Comentarios?

+2

Si está siguiendo ese camino, me pregunto si se podrían usar las macros * DECLARE * y * DEFINITION *:' DECLARE (void foo()) \ nDEFINE ({\ n. ... \ n}) 'donde en el modo de inclusión' DECLARE' agrega ';' y 'DEFINE' se resuelve en nada ... Tal vez más legible, incluso si yo no lo recomendaría (y es solo azúcar sintáctico, todo el los mismos problemas siguen ahí) –

0

Aprenda a reconocer que los archivos de encabezado son algo bueno. Separan cómo se le aparecen los códigos a otro usuario desde la implementación de cómo realmente realiza sus operaciones.

Cuando utilizo el código de otra que hago ahora quieren tener que lidiar con todas la puesta en práctica de ver lo que los métodos están en una clase.Me importa lo que hace el código, no cómo lo hace.

1

Entiendo sus problemas. Diría que el problema principal de C++ es el método de compilación/compilación que heredó de C. La estructura del encabezado C/C++ se ha diseñado en momentos en que la codificación implicaba menos definiciones y más implementaciones. No me tires botellas, pero así es como se ve.

Desde entonces, el OOP ha conquistado el mundo y el mundo es más acerca de las definiciones y las implementaciones. Como resultado, incluir encabezados resulta bastante doloroso para trabajar con un lenguaje en el que las colecciones fundamentales, como las de STL, se crean con plantillas que son un trabajo notoriamente difícil para el compilador. Toda esa magia con los encabezados precompilados no ayuda mucho cuando se trata de TDD, herramientas de refactorización, el entorno de desarrollo general.

Por supuesto, los programadores de C no sufren esto demasiado, ya que no tienen archivos de encabezado pesados ​​en el compilador, por lo que están contentos con la cadena de herramientas de compilación bastante sencilla y de bajo nivel. Con C++ esto es un historial de sufrimiento: declaraciones interminables, encabezados precompilados, analizadores externos, preprocesadores personalizados, etc.

Mucha gente, sin embargo, no se da cuenta de que el C++ es el ÚNICO lenguaje que tiene soluciones fuertes y modernas para - y problemas de bajo nivel. Es fácil decir que debe buscar otro idioma con el sistema de construcción y reflexión adecuado, pero no tiene sentido que tengamos que sacrificar las soluciones de programación de bajo nivel con eso y tenemos que complicar las cosas con un lenguaje de bajo nivel mezclado con alguna solución basada en máquina virtual/JIT.

Tengo esta idea desde hace algún tiempo, que sería lo mejor de la tierra tener una cadena de herramientas C++ basada en "unidades", similar a la de D. El problema surge con la multiplataforma parte: los archivos de objeto pueden almacenar cualquier información, no hay problema con eso, pero dado que en Windows la estructura del archivo de objeto es diferente a la del ELF, sería una molestia implementar una solución multiplataforma para almacenar y procesar las unidades de compilación a mitad de camino.

+1

Hay (en realidad * era * y * será *, en realidad no lo están haciendo ahora) trabajan en un * módulo * del sistema para C++ [n2073] (http: //www.open-std .org/jtc1/sc22/wg21/docs/papers/2006/n2073.pdf) que se eliminó de C++ 0x (* fue *) para ser abordado en una Revisión técnica (* será *). –

+0

Soy consciente de esa propuesta, pero me parece poco probable que se implemente pronto, pero ¡ojalá! Llegar a la raíz del problema es que este sería el mayor cambio de arquitectura en la historia de C++: la base de código existente (código basado en inclusión de definición) se mezclaría con unidades de compilación basadas en módulos y eso complicará bastante las cosas. Cruzar los dedos para la propuesta sin embargo! – progician

1

Es completamente posible desarrollar sin archivos de encabezado. Se puede incluir un archivo fuente directamente:

#include "MyModule.c" 

El principal problema con esto es una de las dependencias circulares (es decir: en C debe declarar una función antes de llamar). Esto no es un problema si diseñas tu código completamente de arriba hacia abajo, pero puede llevarte algo de tiempo ajustar tu cabeza a este tipo de patrón de diseño si no estás acostumbrado.

Si absolutamente debe tener dependencias circulares, es posible que desee considerar la creación de un archivo específicamente para declaraciones e incluirlo antes que cualquier otra cosa. Esto es un poco inconveniente, pero aún menos contaminado que tener un encabezado para cada archivo C.

Actualmente estoy desarrollando el uso de este método para uno de mis proyectos principales. Aquí hay un desglose de las ventajas que he experimentado:

  • Mucha menos contaminación de archivos en su árbol de fuentes.
  • Tiempos de compilación más rápidos. (El compilador solo produce un archivo de objeto, main.o)
  • Archivos de creación más simples. (El compilador solo produce un archivo de objeto, main.o)
  • No es necesario "limpiar". Cada construcción es "limpia".
  • Menos código de la placa de la caldera. Menos código = menos errores potenciales.

Descubrí que Gish (un juego de Cryptic Sea, Edmund McMillen) usaba una variación de esta técnica dentro de su propio código fuente.

+1

No se puede decir que incluir archivos con la extensión .c * sin encabezado *, sino * solo encabezado * (cada unidad de traducción que necesita ese código lo incluye, por lo que el comportamiento es el de las bibliotecas * header-only *) –

+0

Todo es semántica. En el nivel central, #include simplemente inserta el archivo especificado en esa línea. –

2

Después de leer todas las otras respuestas, me parece que falta que haya trabajo continuo para agregar soporte para los módulos en el estándar C++. No llegará a C++ 0x, pero la intención es que se aborde en una revisión técnica posterior (en lugar de esperar a un nuevo estándar, que llevará años).

La propuesta que se estaba discutiendo es N2073.

Lo malo es que no lo conseguirás, ni siquiera con los compiladores C++ 0x más recientes. Tendrás que esperar. Mientras tanto, deberá comprometerse entre la exclusividad de las definiciones en bibliotecas de solo encabezado y el costo de compilación.

0

Esto se ha "revivido" gracias a un duplicado ...

En cualquier caso, el concepto de una cabecera es un digno, es decir, separar la interfaz del detalle de implementación. El encabezado describe cómo usar una clase/método, y no cómo lo hace.

El inconveniente es el detalle dentro de los encabezados y todas las soluciones necesarias. Estos son los principales problemas cuando los veo:

  • dependency generation. Cuando se modifica un encabezado, cualquier archivo fuente que incluya este encabezado requiere una recompilación. El problema es, por supuesto, averiguar qué archivos fuente realmente lo usan. Cuando se realiza una compilación "limpia", a menudo es necesario almacenar en caché la información en algún tipo de árbol de dependencias para más adelante.

  • incluyen guardias. Ok, todos sabemos cómo escribir estos pero en un sistema perfecto no sería necesario.

  • detalles privados. Dentro de una clase, debe poner los detalles privados en el encabezado. Sí, el compilador necesita saber el "tamaño" de la clase, pero en un sistema perfecto sería capaz de unir esto en una fase posterior. Esto lleva a todo tipo de soluciones como pImpl y el uso de clases base abstractas, incluso cuando solo tiene una implementación solo porque quiere ocultar una dependencia.

El sistema perfecto trabajaría con

  • definición de clase separada y la declaración
  • Un claro se unen entre estos dos por lo que el compilador sabe que una declaración de la clase y su definición son, y sabría cual es el tamaño de una clase
  • Usted declara using class en lugar de preprocesador #include. El compilador sabe dónde encontrar una clase. Una vez que hayas hecho "usar clase", puedes usar ese nombre de clase sin calificarlo.

Me gustaría saber cómo lo hace D.

Con respecto a si puede usar C++ sin encabezados, diría que no, los necesita para clases base abstractas y biblioteca estándar. Aparte de eso, podrías vivir sin ellos, aunque probablemente no quieras.

+0

En D hay un sistema de módulos que significa que no existe una separación real entre la implementación y la definición (del mismo modo que en java). La única desventaja de esta solución es que no se pueden distribuir las definiciones de solo público como una especie de referencia ... pero diablos, tenemos doxygen (ddoc en el caso de D) para hacer ese trabajo: D I Aunque no estoy tan seguro de que el proceso de compilación D use los módulos en su forma compilada a mitad de camino (es decir, usando algún tipo de definición de interfaz binaria en los archivos objeto en sí mismos ... si lo hace, eso es una característica clave) – progician

+0

Encontrado, el compilador DMD tiene una característica para extraer la interfaz D a un archivo separado (-H interruptor). http://www.digitalmars.com/d/2.0/dmd-windows.html#interface_files Esta es una de las características más importantes que faltan en los módulos de C++ + interfaces de módulos. – progician

2

Nadie ha mencionado Visual-Assist X en Visual Studio 2012 todavía.

Tiene un montón de menús y teclas de acceso rápido que se pueden utilizar para aliviar el dolor de las cabeceras de mantenimiento:

  • copias "Crear" Declaración de la declaración de la función de la función actual en el archivo .hpp.
  • "Refactor .. Cambiar firma" le permite actualizar simultáneamente el archivo .cpp y .h con un solo comando.
  • Alt-O le permite al instante cambiar entre archivo .cpp y .h.
Cuestiones relacionadas