¿Cuáles son los beneficios de una biblioteca de solo encabezado y por qué la escribiría de esa manera en oposición a poner la implementación en un archivo separado?
¿Cuáles son los beneficios de una biblioteca de solo encabezado y por qué la escribiría de esa manera en oposición a poner la implementación en un archivo separado?
Respuestas:
Hay situaciones en las que una biblioteca de solo encabezado es la única opción, por ejemplo, cuando se trata de plantillas.
Tener una biblioteca de solo encabezado también significa que no tiene que preocuparse por las diferentes plataformas donde se puede usar la biblioteca. Cuando separa la implementación, generalmente lo hace para ocultar los detalles de la implementación y distribuir la biblioteca como una combinación de encabezados y bibliotecas ( lib
, dll
's o .so
archivos). Por supuesto, estos deben compilarse para todos los diferentes sistemas operativos / versiones que ofrezca soporte.
También podría distribuir los archivos de implementación, pero eso significaría un paso adicional para el usuario: compilar su biblioteca antes de usarla.
Por supuesto, esto se aplica caso por caso . Por ejemplo, las bibliotecas de solo encabezado a veces aumentantamaño del código y tiempos de compilación.
Beneficios de la biblioteca de solo encabezado:
Desventajas de una biblioteca de solo encabezado:
Archivos de objetos más grandes. Cada método en línea de la biblioteca que se utiliza en algún archivo fuente también obtendrá un símbolo débil, una definición fuera de línea en el archivo de objeto compilado para ese archivo fuente. Esto ralentiza el compilador y también ralentiza el enlazador. El compilador tiene que generar toda esa hinchazón, y luego el enlazador tiene que filtrarlo.
Recopilación más larga. Además del problema de hinchazón mencionado anteriormente, la compilación llevará más tiempo porque los encabezados son intrínsecamente más grandes con una biblioteca de solo encabezado que con una biblioteca compilada. Esos encabezados grandes deberán analizarse para cada archivo fuente que use la biblioteca. Otro factor es que esos archivos de encabezado en una biblioteca de solo #include
encabezado tienen que los encabezados necesarios para las definiciones en línea, así como los encabezados que se necesitarían si la biblioteca se hubiera construido como una biblioteca compilada.
Recopilación más enredada. Obtiene muchas más dependencias con una biblioteca solo de encabezado debido a esos #include
s adicionales necesarios con una biblioteca solo de encabezado. Cambie la implementación de alguna función clave en la biblioteca y es posible que deba volver a compilar todo el proyecto. Realice ese cambio en el archivo fuente de una biblioteca compilada y todo lo que tiene que hacer es volver a compilar ese archivo fuente de biblioteca, actualizar la biblioteca compilada con ese nuevo archivo .o y volver a vincular la aplicación.
Más difícil de leer para el humano. Incluso con la mejor documentación, los usuarios de una biblioteca a menudo tienen que recurrir a leer los encabezados de la biblioteca. Los encabezados en una biblioteca de solo encabezado están llenos de detalles de implementación que obstaculizan la comprensión de la interfaz. Con una biblioteca compilada, todo lo que ve es la interfaz y un breve comentario sobre lo que hace la implementación, y eso suele ser todo lo que desea. Eso es realmente todo lo que debería desear. No debería tener que conocer los detalles de implementación para saber cómo usar la biblioteca.
detail
.
Sé que este es un hilo antiguo, pero nadie ha mencionado interfaces ABI o problemas específicos del compilador. Así que pensé que lo haría.
Esto se basa básicamente en el concepto de que usted escribe una biblioteca con un encabezado para distribuir a las personas o reutiliza usted mismo en lugar de tener todo en un encabezado. Si está pensando en reutilizar un encabezado y archivos de origen y volver a compilarlos en cada proyecto, entonces esto realmente no se aplica.
Básicamente, si compila su código C ++ y crea una biblioteca con un compilador, el usuario intenta usar esa biblioteca con un compilador diferente o una versión diferente del mismo compilador, entonces puede obtener errores del vinculador o un comportamiento extraño en tiempo de ejecución debido a incompatibilidad binaria.
Por ejemplo, los proveedores de compiladores a menudo cambian su implementación de STL entre versiones. Si tiene una función en una biblioteca que acepta un std :: vector, entonces espera que los bytes de esa clase estén organizados de la forma en que se organizaron cuando se compiló la biblioteca. Si, en una nueva versión del compilador, el proveedor ha realizado mejoras de eficiencia en std :: vector, entonces el código del usuario ve la nueva clase que puede tener una estructura diferente y pasa esa nueva estructura a su biblioteca. Todo va cuesta abajo a partir de ahí ... Es por eso que se recomienda no pasar objetos STL a través de los límites de la biblioteca. Lo mismo se aplica a los tipos C Run-Time (CRT).
Mientras se habla del CRT, su biblioteca y el código fuente del usuario generalmente deben estar vinculados al mismo CRT. Con Visual Studio, si crea su biblioteca usando el CRT multiproceso, pero el usuario se vincula con el CRT de depuración multiproceso, tendrá problemas de vínculo porque es posible que su biblioteca no encuentre los símbolos que necesita. No recuerdo qué función era, pero para Visual Studio 2015, Microsoft hizo una función CRT en línea. De repente, estaba en el encabezado, no en la biblioteca CRT, por lo que las bibliotecas que esperaban encontrarlo en el momento del enlace ya no podían hacerlo y esto generó errores de enlace. El resultado fue que estas bibliotecas debían volver a compilarse con Visual Studio 2015.
También puede obtener errores de enlace o comportamientos extraños si usa la API de Windows pero construye con una configuración Unicode diferente para el usuario de la biblioteca. Esto se debe a que la API de Windows tiene funciones que usan cadenas Unicode o ASCII y macros / define que automáticamente usan los tipos correctos según la configuración Unicode del proyecto. Si pasa una cadena a través del límite de la biblioteca que es del tipo incorrecto, las cosas se rompen en tiempo de ejecución. O puede encontrar que el programa no se vincula en primer lugar.
Estas cosas también son válidas para pasar objetos / tipos a través de los límites de la biblioteca de otras bibliotecas de terceros (por ejemplo, un vector Eigen o una matriz GSL). Si la biblioteca de terceros cambia su encabezado entre usted compilando su biblioteca y su usuario compilando su código, entonces las cosas se romperán.
Básicamente, para estar seguro, lo único que puede pasar a través de los límites de la biblioteca son los tipos integrados y los datos antiguos sin formato (POD). Idealmente, cualquier POD debería estar en estructuras que se definan en sus propios encabezados y no dependan de encabezados de terceros.
Si proporciona una biblioteca de solo encabezado, todo el código se compila con la misma configuración del compilador y con los mismos encabezados, por lo que muchos de estos problemas desaparecen (siempre que la versión de las bibliotecas de terceros que usted y su usuario usen sean compatibles con API).
Sin embargo, hay aspectos negativos que se han mencionado anteriormente, como el aumento del tiempo de compilación. Además, es posible que esté dirigiendo un negocio, por lo que es posible que no desee entregar todos los detalles de implementación del código fuente a todos sus usuarios en caso de que uno de ellos se lo robe.
El principal "beneficio" es que requiere que entregue el código fuente, por lo que terminará con informes de error en las máquinas y con compiladores de los que nunca ha oído hablar. Cuando la biblioteca es completamente de plantillas, no tiene muchas opciones, pero cuando tiene la opción, el encabezado solo suele ser una mala elección de ingeniería. (Por otro lado, por supuesto, el encabezado solo significa que no tiene que documentar ningún procedimiento de integración).
La integración se puede realizar mediante la optimización del tiempo de enlace (LTO)
Me gustaría resaltar esto ya que disminuye el valor de una de las dos ventajas principales de las bibliotecas de solo encabezado: "necesita definiciones en un encabezado para insertarlas".
Un ejemplo concreto mínimo de esto se muestra en: Optimización del tiempo de enlace y en línea
Así que simplemente pasa una bandera, y la inserción se puede hacer en archivos de objeto sin ningún trabajo de refactorización, ya no es necesario mantener definiciones en los encabezados para eso.
Sin embargo, LTO también puede tener sus propias desventajas: ¿Hay alguna razón por la que no se debe utilizar la optimización del tiempo de enlace (LTO)?