Há um problema muito real com as bibliotecas compartilhadas que o idioma pimpl contorna perfeitamente que os virtuais puros não: você não pode modificar / remover membros de dados de uma classe com segurança sem forçar os usuários da classe a recompilar seu código. Isso pode ser aceitável em algumas circunstâncias, mas não para bibliotecas de sistema, por exemplo.
Para explicar o problema em detalhes, considere o seguinte código em sua biblioteca / cabeçalho compartilhado:
// header
struct A
{
public:
A();
// more public interface, some of which uses the int below
private:
int a;
};
// library
A::A()
: a(0)
{}
O compilador emite código na biblioteca compartilhada que calcula o endereço do inteiro a ser inicializado com um certo deslocamento (provavelmente zero neste caso, porque é o único membro) do ponteiro para o objeto A que ele sabe ser this
.
No lado do usuário do código, um new A
primeiro alocará sizeof(A)
bytes de memória e, em seguida, passará um ponteiro para essa memória ao A::A()
construtor comothis
.
Se em uma revisão posterior de sua biblioteca você decidir descartar o inteiro, torná-lo maior, menor ou adicionar membros, haverá uma incompatibilidade entre a quantidade de memória alocada pelo código do usuário e os deslocamentos que o código do construtor espera. O resultado provável é um travamento, se você tiver sorte - se você tiver menos sorte, seu software se comporta de maneira estranha.
Ao pimpl'ing, você pode adicionar e remover membros de dados da classe interna com segurança, conforme a alocação de memória e a chamada do construtor acontecem na biblioteca compartilhada:
// 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())
{}
Tudo o que você precisa fazer agora é manter sua interface pública livre de membros de dados que não sejam o ponteiro para o objeto de implementação, e você estará protegido contra essa classe de erros.
Edit: talvez eu deva acrescentar que a única razão pela qual estou falando sobre o construtor aqui é que eu não queria fornecer mais código - a mesma argumentação se aplica a todas as funções que acessam membros de dados.