C'è un problema molto reale con le librerie condivise che l'idioma pimpl elude ordinatamente che i puri virtuali non possono: non puoi modificare / rimuovere in modo sicuro i membri dei dati di una classe senza costringere gli utenti della classe a ricompilare il loro codice. Ciò può essere accettabile in alcune circostanze, ma non ad esempio per le librerie di sistema.
Per spiegare il problema in dettaglio, considera il seguente codice nella tua libreria / intestazione condivisa:
// header
struct A
{
public:
A();
// more public interface, some of which uses the int below
private:
int a;
};
// library
A::A()
: a(0)
{}
Il compilatore emette il codice nella libreria condivisa che calcola l'indirizzo dell'intero da inizializzare per essere un certo offset (probabilmente zero in questo caso, perché è l'unico membro) dal puntatore all'oggetto A che sa di essere this
.
Sul lato utente del codice, a new A
prima allocherà sizeof(A)
byte di memoria, quindi passerà un puntatore a quella memoria al A::A()
costruttore come this
.
Se in una revisione successiva della libreria decidi di eliminare il numero intero, renderlo più grande, più piccolo o aggiungere membri, ci sarà una mancata corrispondenza tra la quantità di memoria allocata dal codice dell'utente e gli offset che il codice del costruttore si aspetta. Il probabile risultato è un crash, se sei fortunato - se sei meno fortunato, il tuo software si comporta in modo strano.
Con pimpl'ing, puoi aggiungere e rimuovere in modo sicuro membri di dati alla classe interna, poiché l'allocazione della memoria e la chiamata al costruttore avvengono nella libreria condivisa:
// 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())
{}
Tutto quello che devi fare ora è mantenere la tua interfaccia pubblica libera da membri di dati diversi dal puntatore all'oggetto di implementazione e sei al sicuro da questa classe di errori.
Modifica: forse dovrei aggiungere che l'unico motivo per cui sto parlando del costruttore qui è che non volevo fornire più codice: la stessa argomentazione si applica a tutte le funzioni che accedono ai membri dei dati.