2009-11-21 7 views
30

Estoy diseñando una API para una biblioteca C++ que se distribuirá en un dll/objeto compartido. La biblioteca contiene clases polimórficas con funciones virtuales. Me preocupa que si expongo estas funciones virtuales en la DLL API, me recorte de la posibilidad de extender las mismas clases con más funciones virtuales sin romper la compatibilidad binaria con las aplicaciones creadas para la versión anterior de la biblioteca.Cómo diseñar una API C++ para la extensibilidad compatible binaria

Una opción sería utilizar el lenguaje PImpl para ocultar todas las clases que tienen funciones virtuales, sino que también parecen tener sus limitaciones: de esta manera las aplicaciones pierden la posibilidad de crear subclases de las clases de la biblioteca y sustituyendo los métodos virtuales.

¿Cómo diseñarías una clase de API que se puede subclasificar en una aplicación, sin perder la posibilidad de ampliar la API con métodos virtuales (no abstractos) en una nueva versión de la DLL mientras permaneces compatible con versiones anteriores binarias?

Actualización: las plataformas de destino para la biblioteca son windows/msvc y linux/gcc.

Respuesta

29

Hace varios meses escribí un artículo titulado "Compatibilidad binaria de bibliotecas compartidas implementadas en C++ en sistemas GNU/Linux" [pdf]. Si bien los conceptos son similares en el sistema de Windows, estoy seguro de que no son exactamente lo mismo. Pero después de leer el artículo, puede obtener una noción de lo que está sucediendo en el nivel binario de C++ que tiene algo que ver con la compatibilidad.

Por cierto, la interfaz binaria de la aplicación GCC se resume en un borrador de documento estándar "Itanium ABI", por lo que tendrá una base formal para un estándar de codificación que elija.

Solo para un ejemplo rápido: en GCC puede ampliar una clase con más funciones virtuales, si ninguna otra clase lo hereda. Lee el artículo para un mejor conjunto de reglas.

Pero de todos modos, las reglas a veces son demasiado complejas para comprenderlas. Por lo tanto, podría interesarle una herramienta que verifique la compatibilidad de dos versiones determinadas: abi-compliance-checker para Linux.

+0

El host para el archivo PDF que ha publicado parece estar hecho. ¿Podrías volver a publicarlo, por favor? –

+0

@ MichałGórny parece estar de nuevo encendido, pero lo he vuelto a crear [aquí] (http://static.coldattic.info/restricted/science/syrcose09/cppbincomp.pdf) por si acaso. –

1

C++ compatibilidad binaria es generalmente difícil, incluso sin herencia. Mira a GCC por ejemplo. En los últimos 10 años, no estoy seguro de cuántos cambios ABI han tenido. Entonces MSVC tiene un conjunto diferente de convenciones, por lo que no se puede vincular a eso con GCC y viceversa ... Si lo comparas con el mundo C, el compilador interoperativo parece un poco mejor allí.

Si está en Windows debería mirar COM. A medida que introduce nuevas funciones, puede agregar interfaces. A continuación, las personas que llaman pueden llamar al QueryInterface() para exponer esa nueva funcionalidad, e incluso si terminan cambiando mucho las cosas, puede dejar la implementación anterior allí o escribir calces para las interfaces anteriores.

+4

"En los últimos 10 años, no estoy seguro de cuántos cambios ABI han tenido". Déjame decirte cuántos. ** UNO. ** El ABI actual se formaliza y describe en un documento estándar. –

+1

Sé que hubo una importante ruptura entre 2.95 y 3.0 (que ha sido un problema grave en BeOS y Haiku), pero me parece recordar otra vez importante ruptura entre 3,2 y 3,3 o menos (lo que causó un poco de problemas en Gentoo) . ¿Es esto incorrecto? – greyfade

+1

Oh, pensé que 3.0 tenía más de 10 años. Sí, dos. Uno en junio de 2001, con lanzamiento de 3.0. Desde entonces trabajaron para producir un buen diseño de ABI de vida larga y lo adoptaron con la versión 3.2 en agosto de 2002. Hace siete años fue el último. –

11

Hay un interesante artículo sobre la base de conocimientos de KDE que se describe y no hacer de la DO cuando el objetivo de la compatibilidad binaria al escribir una biblioteca: Policies/Binary Compatibility Issues With C++

1

Creo que usted no entiende el problema de la creación de subclases.

Aquí es su Pimpl:

// .h 
class Derived 
{ 
public: 
    virtual void test1(); 
    virtual void test2(); 
private; 
    Impl* m_impl; 
}; 

// .cpp 
struct Impl: public Base 
{ 
    virtual void test1(); // override Base::test1() 
    virtual void test2(); // override Base::test2() 

    // data members 
}; 

void Derived::test1() { m_impl->test1(); } 
void Derived::test2() { m_impl->test2(); } 

Ver?No hay problema con anular los métodos virtuales de Base, solo tiene que asegurarse de redeclararlos virtual en Derived para que los derivados de Derived sepan que también pueden reescribirlos (solo si así lo desea, que por cierto es una excelente forma de proporcionando un final para quienes carecen de él), y aún puede redefinirlo usted mismo en Impl, que incluso puede llamar a la versión Base. No hay problema con Pimpl allí.

Por otro lado, pierde polimorfismo, lo que puede ser molesto. Depende de usted decidir si quiere polimorfismo o solo composición.

+0

La clase contenedora de Pimpl debe tener métodos no virtuales, ya que en este caso se usa exactamente para ocultar los métodos virtuales de las clases de biblioteca. Si los métodos virtuales estuvieran presentes en la interfaz de la biblioteca, sería imposible ampliar la interfaz de la biblioteca en nuevas versiones con más métodos virtuales mientras se mantiene la compatibilidad binaria. Pero si la interfaz publicada no es virtual, ¿cómo lo subclonarán los clientes? De ahí la publicación. – shojtsy

+1

Bien, entonces entiendo tu punto. Pero en este momento no es realmente un problema de Pimpl. Más un problema sobre el uso de métodos 'virtuales' en la interfaz. –

+0

"solo tiene que asegurarse de volver a declararlos virtuales en Derivado para que los derivados de Derived puedan reescribirlos también". No, los métodos virtuales anulados también son implícitamente virtuales. –

0

Si expone la clase PImpl en un archivo de encabezado, puede heredar de ella. Todavía puede mantener la portabilidad hacia atrás ya que las clases externas contienen un puntero al objeto PImpl. Por supuesto, si el código de cliente de la biblioteca no es muy inteligente, podría hacer un mal uso de este objeto PImpl expuesto y arruinar la compatibilidad binaria con versiones anteriores. Puede agregar algunas notas para advertir al usuario en el archivo de encabezado de PImpl.

Cuestiones relacionadas