Pimpl-Idiom vs. reine virtuelle Klassenschnittstelle

Lesezeit: 10 Minuten

Pimpl Idiom vs reine virtuelle Klassenschnittstelle
Arkaitz Jimenez

Ich habe mich gefragt, was einen Programmierer dazu bringen würde, entweder Pimpl-Idiom oder reine virtuelle Klasse und Vererbung zu wählen.

Ich verstehe, dass das Pimpl-Idiom mit einer expliziten zusätzlichen Indirektion für jede öffentliche Methode und dem Overhead für die Objekterstellung einhergeht.

Die reine virtuelle Klasse hingegen enthält eine implizite Indirektion (vtable) für die erbende Implementierung, und ich verstehe, dass kein Overhead für die Objekterstellung anfällt.
BEARBEITEN: Aber Sie brauchen eine Fabrik, wenn Sie das Objekt von außen erstellen

Was macht den rein virtuellen Unterricht weniger begehrenswert als das Pickel-Idiom?

1643566816 322 Pimpl Idiom vs reine virtuelle Klassenschnittstelle
Paul Hollingworth

Beim Schreiben einer C++-Klasse ist es angebracht, darüber nachzudenken, ob dies der Fall sein wird

  1. Ein Werttyp

    Nach Wert kopieren, Identität ist nie wichtig. Es ist angemessen, dass es ein Schlüssel in einer std::map ist. Beispiel: eine „String“-Klasse oder eine „Datum“-Klasse oder eine „komplexe Zahl“-Klasse. Instanzen einer solchen Klasse zu „kopieren“ ist sinnvoll.

  2. Ein Entitätstyp

    Identität ist wichtig. Wird immer als Referenz übergeben, niemals als “Wert”. Oft macht es keinen Sinn, Instanzen der Klasse überhaupt zu “kopieren”. Wenn es sinnvoll ist, ist eine polymorphe “Clone”-Methode normalerweise besser geeignet. Beispiele: Eine Socket-Klasse, eine Datenbankklasse, eine “Richtlinien”-Klasse, alles, was in einer funktionalen Sprache ein “Abschluss” wäre.

Sowohl pImpl als auch die reine abstrakte Basisklasse sind Techniken, um Abhängigkeiten von der Kompilierzeit zu reduzieren.

Ich verwende pImpl jedoch immer nur, um Werttypen (Typ 1) zu implementieren, und nur manchmal, wenn ich Kopplungs- und Kompilierzeitabhängigkeiten wirklich minimieren möchte. Oft lohnt sich die Mühe nicht. Wie Sie zu Recht betonen, gibt es mehr syntaktischen Overhead, weil Sie Weiterleitungsmethoden für alle öffentlichen Methoden schreiben müssen. Für Typ-2-Klassen verwende ich immer eine reine abstrakte Basisklasse mit zugehörigen Factory-Methoden.

  • Bitte lesen Sie den Kommentar von Paul de Vrieze zu dieser Antwort. Pimpl und Pure Virtual unterscheiden sich erheblich, wenn Sie sich in einer Bibliothek befinden und Ihre .so/.dll austauschen möchten, ohne den Client neu zu erstellen. Clients verlinken namentlich auf Pimpl-Frontends, sodass es ausreicht, alte Methodensignaturen beizubehalten. OTOH im reinen abstrakten Fall verknüpfen sie effektiv durch den Vtable-Index, sodass das Neuordnen von Methoden oder das Einfügen in die Mitte die Kompatibilität beeinträchtigt.

    – Schlange

    25. November 15 um 11:13 Uhr

  • Sie können nur Methoden in einem Frontend der Pimpl-Klasse hinzufügen (oder neu anordnen), um die binäre Vergleichbarkeit aufrechtzuerhalten. Logischerweise haben Sie die Schnittstelle immer noch geändert und es scheint etwas zwielichtig zu sein. Die Antwort hier ist ein sinnvoller Ausgleich, der auch beim Unit-Testing per „Dependency Injection“ helfen kann; aber die Antwort hängt immer von den Anforderungen ab. Autoren von Drittanbieterbibliotheken (im Gegensatz zur Verwendung einer Bibliothek in Ihrer eigenen Organisation) bevorzugen möglicherweise Pimpl.

    – Spacen Jasset

    10. November 18 um 17:38 Uhr

Pointer to implementation In der Regel geht es darum, strukturelle Implementierungsdetails zu verbergen. Interfaces geht es darum, verschiedene Implementierungen zu instanziieren. Sie dienen eigentlich zwei verschiedenen Zwecken.

  • nicht unbedingt, ich habe Klassen gesehen, die je nach gewünschter Implementierung mehrere Pickel speichern. Oft ist dies beispielsweise ein Win32-Impl im Vergleich zu einem Linux-Impl von etwas, das je nach Plattform unterschiedlich implementiert werden muss.

    – Doug T.

    5. Mai 09 um 14:10 Uhr

  • Sie können jedoch eine Schnittstelle verwenden, um die Implementierungsdetails zu entkoppeln und zu verbergen

    – Arkaitz Jimenez

    5. Mai 09 um 14:11 Uhr

  • Während Sie Pimpl über eine Schnittstelle implementieren können, gibt es oft keinen Grund, die Implementierungsdetails zu entkoppeln. Es gibt also keinen Grund, polymorph zu werden. Der Grund für pimpl ist es, Implementierungsdetails vom Client fernzuhalten (in C++, um sie aus dem Header herauszuhalten). Sie könnten dies mit einer abstrakten Basis/Schnittstelle tun, aber im Allgemeinen ist das unnötiger Overkill.

    – Michael Burr

    5. Mai 09 um 14:38 Uhr

  • Warum ist es übertrieben? Ich meine, ist die Interface-Methode langsamer als die Pickel-Methode? Es mag logische Gründe geben, aber aus praktischer Sicht würde ich sagen, dass es einfacher ist, es mit einer abstrakten Schnittstelle zu tun

    – Arkaitz Jimenez

    5. Mai ’09 um 14:40 Uhr

  • Ich würde sagen, dass eine abstrakte Basisklasse/Schnittstelle die “normale” Art ist, Dinge zu tun, und ein einfacheres Testen durch Mocking ermöglicht

    – paulm

    6. März 15 um 23:30 Uhr

Pimpl Idiom vs reine virtuelle Klassenschnittstelle
Pontus-Gagge

Das Pimpl-Idiom hilft Ihnen, Abhängigkeiten und Zeiten für den Build zu reduzieren, insbesondere in großen Anwendungen, und minimiert die Header-Exponierung der Implementierungsdetails Ihrer Klasse für eine Kompilierungseinheit. Die Benutzer Ihrer Klasse sollten sich der Existenz eines Pickels nicht einmal bewusst sein müssen (außer als kryptischer Hinweis, in den sie nicht eingeweiht sind!).

Abstrakte Klassen (rein virtuelle) sind etwas, dessen sich Ihre Clients bewusst sein müssen: Wenn Sie versuchen, sie zu verwenden, um Kopplungs- und Zirkelbezüge zu reduzieren, müssen Sie ihnen eine Möglichkeit hinzufügen, Ihre Objekte zu erstellen (z. B. durch Factory-Methoden oder Klassen, Abhängigkeitsinjektion oder andere Mechanismen).

1643566817 232 Pimpl Idiom vs reine virtuelle Klassenschnittstelle
Ilias Bartolini

Ich suchte eine Antwort auf die gleiche Frage. Nach dem Lesen einiger Artikel und etwas Übung Ich bevorzuge die Verwendung von “Pure Virtual Class Interfaces”.

  1. Sie sind geradliniger (dies ist eine subjektive Meinung). Pimpl Idiom gibt mir das Gefühl, dass ich Code “für den Compiler” schreibe, nicht für den “nächsten Entwickler”, der meinen Code lesen wird.
  2. Einige Testframeworks bieten direkte Unterstützung für das Mocking von rein virtuellen Klassen
  3. Es ist wahr, dass Sie müssen eine von außen zugängliche Fabrik. Aber wenn Sie Polymorphismus nutzen wollen: Das ist auch “pro”, kein “contra”. …und eine einfache Fabrikmethode tut nicht wirklich weh

Einziges Manko (Ich versuche das zu recherchieren) ist, dass das Pickel-Idiom schneller sein könnte

  1. wenn die Proxy-Aufrufe inline sind, während die Vererbung notwendigerweise einen zusätzlichen Zugriff auf das Objekt VTABLE zur Laufzeit benötigt
  2. der Speicherbedarf der Pimpl-Public-Proxy-Klasse ist kleiner (Sie können problemlos Optimierungen für schnellere Swaps und andere ähnliche Optimierungen vornehmen)

Ich hasse Pickel! Sie machen die Klasse hässlich und nicht lesbar. Alle Methoden werden auf Pickel umgeleitet. Sie sehen nie in Headern, welche Funktionalitäten die Klasse hat, also können Sie sie nicht umgestalten (zB einfach die Sichtbarkeit einer Methode ändern). Die Klasse fühlt sich an wie „schwanger“. Ich denke, die Verwendung von Schnittstellen ist besser und wirklich genug, um die Implementierung vor dem Client zu verbergen. Sie können eine Klasse mehrere Schnittstellen implementieren lassen, um sie dünn zu halten. Schnittstellen sollte man bevorzugen! Hinweis: Sie benötigen nicht zwingend die Factory-Klasse. Relevant ist, dass die Klasse Clients mit ihren Instanzen über die entsprechende Schnittstelle kommuniziert. Das Verbergen privater Methoden finde ich eine seltsame Paranoia und sehe keinen Grund dafür, da wir Schnittstellen haben.

  • Es gibt einige Fälle, in denen Sie keine rein virtuellen Schnittstellen verwenden können. Zum Beispiel, wenn Sie einen Legacy-Code haben und Sie zwei Module haben, die Sie trennen müssen, ohne sie zu berühren.

    – AlexTheo

    6. März 14 um 15:19 Uhr

  • Wie @Paul de Vrieze unten betont, verlieren Sie die ABI-Kompatibilität, wenn Sie die Methoden der Basisklasse ändern, da Sie eine implizite Abhängigkeit von der vtable der Klasse haben. Ob dies ein Problem darstellt, hängt vom Anwendungsfall ab.

    – H. Rittich

    31. Januar 19 um 12:15 Uhr


  • “Das Verstecken privater Methoden finde ich eine seltsame Paranoia” Erlaubt Ihnen das nicht, die Abhängigkeiten zu verbergen und damit die Kompilierungszeit zu minimieren, wenn sich eine Abhängigkeit ändert?

    – pooya13

    24. April 2020 um 21:08 Uhr


  • Ich verstehe auch nicht, wie Fabriken einfacher zu refaktorisieren sind als pImpl. Verlassen Sie nicht in beiden Fällen die “Schnittstelle” und ändern die Implementierung? In Factory müssen Sie eine .h- und eine .cpp-Datei ändern und in pImpl müssen Sie eine .h- und zwei .cpp-Dateien ändern, aber das war es auch schon und Sie müssen die cpp-Datei der pImpl-Schnittstelle im Allgemeinen nicht ändern.

    – pooya13

    24. April 2020 um 21:16 Uhr

Es gibt ein sehr reales Problem mit gemeinsam genutzten Bibliotheken, das das Pimpl-Idiom sauber umgeht, was reine Virtuelle nicht können: Sie können Datenmitglieder einer Klasse nicht sicher ändern/entfernen, ohne die Benutzer der Klasse zu zwingen, ihren Code neu zu kompilieren. Das mag unter Umständen akzeptabel sein, aber nicht zB für Systembibliotheken.

Um das Problem im Detail zu erklären, betrachten Sie den folgenden Code in Ihrer Shared Library/Header:

// 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 so berechnet, dass sie einen bestimmten Offset (in diesem Fall wahrscheinlich Null, weil es das einzige Mitglied ist) vom Zeiger auf das A-Objekt hat, das es ist this.

Auf der Benutzerseite des Codes a new A wird erstmal vergeben sizeof(A) Bytes Speicher, dann übergeben Sie einen Zeiger auf diesen Speicher an die A::A() Konstrukteur als this.

Wenn Sie sich in einer späteren Überarbeitung Ihrer Bibliothek entscheiden, die ganze Zahl zu löschen, sie größer oder kleiner zu machen oder Mitglieder hinzuzufügen, wird es eine Diskrepanz zwischen der Menge an Speicher geben, die der Code des Benutzers zuweist, und den Offsets, die der Konstruktorcode erwartet. Das wahrscheinliche Ergebnis ist ein Absturz, wenn Sie Glück haben – wenn Sie weniger Glück haben, verhält sich Ihre Software seltsam.

Durch Pimpl’ing können Sie Datenmember sicher zur inneren Klasse hinzufügen und 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 Art von Fehlern 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.

  • Es gibt einige Fälle, in denen Sie keine rein virtuellen Schnittstellen verwenden können. Zum Beispiel, wenn Sie einen Legacy-Code haben und Sie zwei Module haben, die Sie trennen müssen, ohne sie zu berühren.

    – AlexTheo

    6. März 14 um 15:19 Uhr

  • Wie @Paul de Vrieze unten betont, verlieren Sie die ABI-Kompatibilität, wenn Sie die Methoden der Basisklasse ändern, da Sie eine implizite Abhängigkeit von der vtable der Klasse haben. Ob dies ein Problem darstellt, hängt vom Anwendungsfall ab.

    – H. Rittich

    31. Januar 19 um 12:15 Uhr


  • “Das Verstecken privater Methoden finde ich eine seltsame Paranoia” Erlaubt Ihnen das nicht, die Abhängigkeiten zu verbergen und damit die Kompilierungszeit zu minimieren, wenn sich eine Abhängigkeit ändert?

    – pooya13

    24. April 2020 um 21:08 Uhr


  • Ich verstehe auch nicht, wie Fabriken einfacher zu refaktorisieren sind als pImpl. Verlassen Sie nicht in beiden Fällen die “Schnittstelle” und ändern die Implementierung? In Factory müssen Sie eine .h- und eine .cpp-Datei ändern und in pImpl müssen Sie eine .h- und zwei .cpp-Dateien ändern, aber das war es auch schon und Sie müssen die cpp-Datei der pImpl-Schnittstelle im Allgemeinen nicht ändern.

    – pooya13

    24. April 2020 um 21:16 Uhr

1643566818 956 Pimpl Idiom vs reine virtuelle Klassenschnittstelle
Sam

Wir dürfen nicht vergessen, dass Vererbung eine stärkere, engere Kopplung ist als Delegation. Ich würde auch alle in den gegebenen Antworten aufgeworfenen Probleme berücksichtigen, wenn ich entscheide, welche Designsprachen zur Lösung eines bestimmten Problems verwendet werden sollen.

.

704170cookie-checkPimpl-Idiom vs. reine virtuelle Klassenschnittstelle

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy