Es gibt ein sehr reales Problem mit gemeinsam genutzten Bibliotheken, das das Pimpl-Idiom genau umgeht, was reine Virtuals nicht können: Sie können Datenelemente einer Klasse nicht sicher ändern / entfernen, ohne Benutzer der Klasse zu zwingen, ihren Code neu zu kompilieren. Dies kann unter Umständen akzeptabel sein, z. B. nicht für Systembibliotheken.
Beachten Sie den folgenden Code in Ihrer gemeinsam genutzten Bibliothek / Ihrem Header, um das Problem im Detail zu erläutern:
// header
struct A
{
public:
A();
// more public interface, some of which uses the int below
private:
int a;
};
// library
A::A()
: a(0)
{}
Der Compiler gibt Code in der gemeinsam genutzten Bibliothek aus, der die Adresse der zu initialisierenden Ganzzahl mit einem bestimmten Offset (in diesem Fall wahrscheinlich Null, da dies das einzige Element ist) vom Zeiger auf das ihm bekannte A-Objekt berechnet this
.
Auf der Benutzerseite des Codes weist a new A
zuerst sizeof(A)
Speicherbytes zu und übergibt dann dem A::A()
Konstruktor als einen Zeiger auf diesen Speicher alsthis
.
Wenn Sie in einer späteren Version Ihrer Bibliothek die Ganzzahl löschen, vergrößern, verkleinern oder Mitglieder hinzufügen möchten, besteht eine Nichtübereinstimmung zwischen der Menge des zugewiesenen Speicherbenutzercodes und den vom Konstruktorcode erwarteten Offsets. Das wahrscheinliche Ergebnis ist ein Absturz, wenn Sie Glück haben - wenn Sie weniger Glück haben, verhält sich Ihre Software merkwürdig.
Durch Pimpl'ing können Sie Datenelemente sicher zur inneren Klasse hinzufügen und daraus entfernen, da die Speicherzuweisung und der Konstruktoraufruf in der gemeinsam genutzten Bibliothek erfolgen:
// 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())
{}
Alles, was Sie jetzt tun müssen, ist, Ihre öffentliche Schnittstelle frei von anderen Datenelementen als dem Zeiger auf das Implementierungsobjekt zu halten, und Sie sind vor dieser Fehlerklasse sicher.
Bearbeiten: Ich sollte vielleicht hinzufügen, dass der einzige Grund, warum ich hier über den Konstruktor spreche, darin besteht, dass ich nicht mehr Code bereitstellen wollte - die gleiche Argumentation gilt für alle Funktionen, die auf Datenelemente zugreifen.