compilers - C++-cuando se requiere la recompilación
c++ 17 download (7)
Tienes una clase de la que dependen muchas bibliotecas. Necesitas modificar la clase para una aplicación. ¿Cuál de los siguientes cambios requiere recompilar todas las bibliotecas antes de que sea seguro compilar la aplicación?
- agregar un constructor
- agregar un miembro de datos
- cambiar destructor en virtual
- agregar un argumento con valor predeterminado a una función miembro existente
Gracias
Al usar el archivo .def de exportación ordinal para mantener la interfaz binaria de la aplicación, puede evitar la recompilación del cliente en muchos casos:
Añadir un constructor
Exporte esta función constructora al final de la tabla de exportación con el mayor número ordinal. Cualquier código de cliente que no llame a este constructor no necesita compilar.
Añadir un miembro de datos
Esto es una ruptura si el código del cliente manipula el objeto de la clase directamente, no a través del puntero o la referencia.
Cambiar destructor en virtual
Probablemente esto sea un descanso, si su clase no tiene ninguna otra función virtual, lo que significa que ahora su clase tiene que agregar una tabla vptr y aumentar el tamaño del objeto de la clase y cambiar la distribución de la memoria. Si su clase ya tiene una tabla vptr, mover el destructor al final de la tabla vptr no afectará el diseño del objeto en términos de compatibilidad con versiones anteriores. Pero si la clase de cliente se deriva de su clase y ha definido su propia función virtual, entonces se rompe. Y también cualquier cliente que llame al destructor no virtual original se romperá.
Agregar un argumento con valor predeterminado a una función miembro existente
Esto es definitivamente un descanso.
Estoy claramente en contra de la respuesta de @sbi: en general necesitas recompilar. Solo en circunstancias mucho más estrictas que las que él publicó, puedes escapar.
- agregar un constructor
Si el constructor agregado es el constructor predeterminado o el constructor de copia, cualquier código que use la versión implícitamente definida y no se vuelva a compilar no podrá inicializar el objeto, y eso significa que las invariantes requeridas por otros métodos no se establecerán en construcción, es decir, el código fallará.
- agregar un miembro de datos
Esto modifica el diseño del objeto. Incluso el código que solo utiliza punteros o referencias debe compilarse para adaptarse al cambio de diseño. Si se agrega un miembro al principio del objeto, cualquier código que use cualquier miembro del objeto se compensará y fallará.
struct test {
// int x; // added later
int y;
};
void foo( test * t ) {
std::cout << t->y << std::endl;
}
Si foo
no fue recompilado, luego de descomentar x
se imprimiría t->x
lugar de t->y
. Si los tipos no coincidieran sería incluso peor. Teóricamente, incluso si el miembro agregado se encuentra al final del objeto, si hay más de un modificador de acceso, el compilador puede reordenar a los miembros y tener el mismo problema.
- cambiar destructor a virtual
Si es el primer método virtual, cambiará el diseño del objeto y obtendrá todos los problemas anteriores más la adición de que la eliminación a través de una referencia a la base llamará al destructor base y no se enviará al método correcto. En la mayoría de los compiladores (con soporte de vtable) puede implicar un cambio en el diseño de la memoria de vtable para el tipo, y eso significa que se puede llamar al método incorrecto y causar estragos.
- agregar un argumento con valor predeterminado
Este es un cambio en la firma de la función, todo el código que usó el método anterior tendrá que volver a compilarse para adaptarse a la nueva firma.
Estrictamente hablando, terminas en tierra de comportamiento indefinido tan pronto como no lo recompiles por ninguna de esas razones.
Dicho esto, en la práctica puedes salirte con algunos de ellos:
- agregar un constructor
Podría estar bien para usar siempre que
- No es el primer constructor definido por el usuario para la clase.
- no es el constructor de copia
- agregar un miembro de datos
Esto cambia el tamaño de las instancias de la clase. Podría estar bien para cualquier persona que solo use punteros o referencias, si tiene cuidado de poner esos datos detrás de todos los demás datos, de modo que las compensaciones para acceder a los otros miembros de los datos no cambien. Pero el diseño exacto de los subobjetos en binario no está definido, por lo que tendrá que confiar en una implementación específica.
- cambiar destructor en virtual
Esto cambia la tabla virtual de la clase, por lo que necesita recompilación.
- agregar un argumento con valor predeterminado a una función miembro existente
Dado que los argumentos predeterminados se insertan en el sitio de la llamada, todos los que usan esto deben volver a compilarlos. (Sin embargo, usar la sobrecarga en lugar de los argumentos predeterminados podría permitirle salirse con la suya).
Tenga en cuenta que cualquier función miembro en línea podría hacer que cualquiera de los errores mencionados anteriormente sea incorrecto, ya que el código de esos está directamente integrado (y optimizado) en el código de los clientes.
Sin embargo, la apuesta más segura sería simplemente compilar todo. ¿Por qué es esto un problema?
Las clases se definen en el archivo de cabecera. El archivo de encabezado se compilará tanto en la biblioteca que implementa la clase como en el código que usa la clase. Supongo que está tomando en cuenta que necesitará recompilar la implementación de la clase después de cambiar el archivo de encabezado de la clase y que la pregunta que hace es si necesitará recompilar cualquier código que haga referencia a la clase.
El problema que está describiendo es uno de compatibilidad binaria (BC) y generalmente sigue las siguientes reglas:
- Agregar funciones no virtuales en cualquier parte de la clase no rompe BC.
- Cambiar cualquier definición de función (agregar parámetros) romperá BC.
- Agregar funciones virtuales en cualquier lugar cambia la tabla v y, por lo tanto, rompe BC.
- Agregar miembros de datos se romperá BC.
- Cambiar un parámetro de no predeterminado a predeterminado no romperá BC.
- Cualquier cambio en las funciones en línea romperá BC (por lo tanto, debe evitarse la función en línea si BC es importante).
- Cambiar el compilador (o, a veces, incluso las versiones del compilador) probablemente romperá el BC a menos que los compiladores se adhieran de manera estricta al mismo ABI.
Si BC es un problema importante para la plataforma que está implementando, podría ser una buena idea separar la interfaz y la implementación utilizando el patrón Bridge .
Además, el lenguaje C ++ no trata con la Interfaz Binaria de la Aplicación (ABI). Si la compatibilidad binaria es un problema importante, probablemente debería consultar la especificación ABI de su plataforma para obtener más detalles.
Editar: actualizado agregando miembros de datos. Esto romperá BC porque ahora se necesitará más memoria para la clase que antes.
Tan pronto como cambie algo en el archivo de encabezado (archivo hpp), tendrá que volver a compilar todo lo que depende de él.
Sin embargo, si cambia el archivo de origen (archivo cpp), debe recompilar solo la biblioteca que contiene las definiciones de necesidades de este archivo.
La manera fácil de romper las dependencias físicas, donde todas las bibliotecas en el nivel superior deben recompilarse, es usar el lenguaje pimpl. Luego, mientras no toque los archivos de encabezado, solo necesita compilar la biblioteca donde se está modificando la implementación.
Todos ellos necesitan recompilar todas las bibliotecas que usan la clase. (siempre que incluyan el archivo .h)
la respuesta de sbi es bastante buena (y merece ser votada hasta arriba). Sin embargo, creo que puedo expandir el "quizás bien" en algo más concreto.
Añadir un constructor
Si el constructor que ha agregado es el constructor predeterminado (o incluso un constructor de copia), entonces debe tener cuidado. Si anteriormente no estaba disponible, el compilador los generará automáticamente (ya que se requiere una recopilación para garantizar que están utilizando el constructor real que se ha implementado). Por esta razón, tiendo a ocultar o definir siempre estos constructores para las clases que forman parte de alguna API.