Hay un problema muy real con las bibliotecas compartidas que el idioma pimpl elude perfectamente y que los virtuales puros no pueden: no se pueden modificar / eliminar de forma segura los miembros de datos de una clase sin obligar a los usuarios de la clase a recompilar su código. Eso puede ser aceptable en algunas circunstancias, pero no, por ejemplo, para las bibliotecas del sistema.
Para explicar el problema en detalle, considere el siguiente código en su biblioteca / encabezado compartido:
// header
struct A
{
public:
A();
// more public interface, some of which uses the int below
private:
int a;
};
// library
A::A()
: a(0)
{}
El compilador emite código en la biblioteca compartida que calcula la dirección del entero que se inicializará para que sea un cierto desplazamiento (probablemente cero en este caso, porque es el único miembro) del puntero al objeto A que sabe que es this
.
En el lado del usuario del código, new A
primero asignará sizeof(A)
bytes de memoria, luego pasará un puntero a esa memoria al A::A()
constructor como this
.
Si en una revisión posterior de su biblioteca decide eliminar el número entero, hacerlo más grande, más pequeño o agregar miembros, habrá una discrepancia entre la cantidad de memoria que el código del usuario asigna y las compensaciones que espera el código del constructor. El resultado probable es un bloqueo, si tiene suerte; si tiene menos suerte, su software se comporta de manera extraña.
Al hacer pimpl'ing, puede agregar y eliminar miembros de datos de forma segura a la clase interna, ya que la asignación de memoria y la llamada al constructor ocurren en la biblioteca compartida:
// header
struct A
{
public:
A();
// more public interface, all of which delegates to the impl
private:
void * impl;
};
// library
A::A()
: impl(new A_impl())
{}
Todo lo que necesita hacer ahora es mantener su interfaz pública libre de miembros de datos que no sean el puntero al objeto de implementación, y está a salvo de esta clase de errores.
Editar: tal vez debería agregar que la única razón por la que estoy hablando del constructor aquí es que no quería proporcionar más código; la misma argumentación se aplica a todas las funciones que acceden a los miembros de datos.