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 die Pimpl-Sprache eine explizite zusätzliche Indirektion für jede öffentliche Methode und den Aufwand für die Objekterstellung enthält.
Die virtuelle Klasse Pure hingegen verfügt über eine implizite Indirektion (vtable) für die erbende Implementierung, und ich verstehe, dass kein Aufwand für die Objekterstellung anfällt.
BEARBEITEN : Sie benötigen jedoch eine Factory, wenn Sie das Objekt von außen erstellen
Was macht die reine virtuelle Klasse weniger wünschenswert als die Pimpl-Sprache?
c++
abstract-class
pimpl-idiom
Arkaitz Jimenez
quelle
quelle
Antworten:
Wenn Sie eine C ++ - Klasse schreiben, sollten Sie darüber nachdenken, ob dies der Fall sein wird
Ein Werttyp
Nach Wert kopieren, Identität ist niemals wichtig. Es ist angemessen, dass es ein Schlüssel in einer std :: map ist. Beispiel: eine "Zeichenfolge" -Klasse oder eine "Datums" -Klasse oder eine "komplexe Zahl" -Klasse. Es ist sinnvoll, Instanzen einer solchen Klasse zu "kopieren".
Ein Entitätstyp
Identität ist wichtig. Immer als Referenz übergeben, niemals als "Wert". Oft macht es keinen Sinn, Instanzen der Klasse überhaupt zu "kopieren". Wenn es sinnvoll ist, ist normalerweise eine polymorphe "Klon" -Methode 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 zur Reduzierung der Abhängigkeiten von der Kompilierungszeit.
Ich verwende pImpl jedoch immer nur zum Implementieren von Werttypen (Typ 1) und nur manchmal, wenn ich Kopplungs- und Kompilierungszeitabhängigkeiten wirklich minimieren möchte. Oft ist es die Mühe nicht wert. Wie Sie zu Recht betonen, ist der syntaktische Aufwand höher, da Sie Weiterleitungsmethoden für alle öffentlichen Methoden schreiben müssen. Für Klassen vom Typ 2 verwende ich immer eine reine abstrakte Basisklasse mit zugehörigen Factory-Methoden.
quelle
Pointer to implementation
In der Regel geht es darum, strukturelle Implementierungsdetails zu verbergen.Interfaces
Es geht darum, verschiedene Implementierungen zu instanziieren. Sie dienen wirklich zwei verschiedenen Zwecken.quelle
Die Pimpl-Sprache hilft Ihnen dabei, Build-Abhängigkeiten und -Zeiten zu reduzieren, insbesondere in großen Anwendungen, und minimiert die Header-Exposition der Implementierungsdetails Ihrer Klasse gegenüber einer Kompilierungseinheit. Die Benutzer Ihrer Klasse sollten sich nicht einmal der Existenz eines Pickels bewusst sein müssen (außer als kryptischer Zeiger, auf den sie nicht eingeweiht sind!).
Abstrakte Klassen (reine virtuelle Klassen) müssen Ihren Kunden bewusst sein: Wenn Sie versuchen, sie zu verwenden, um Kopplungen und Zirkelverweise zu reduzieren, müssen Sie eine Möglichkeit hinzufügen, die es ihnen ermöglicht, Ihre Objekte zu erstellen (z. B. über Factory-Methoden oder -Klassen). Abhängigkeitsinjektion oder andere Mechanismen).
quelle
Ich suchte nach einer Antwort auf dieselbe Frage. Nachdem ich einige Artikel gelesen und geübt habe, bevorzuge ich die Verwendung von "Pure Virtual Class Interfaces" .
Der einzige Nachteil ( ich versuche dies zu untersuchen ) ist, dass die Zuhälter-Sprache schneller sein könnte
quelle
Ich hasse Pickel! Sie machen den Unterricht hässlich und nicht lesbar. Alle Methoden werden auf Pickel umgeleitet. In Headern wird nie angezeigt, welche Funktionen die Klasse hat, daher können Sie sie nicht umgestalten (z. B. einfach die Sichtbarkeit einer Methode ändern). Die Klasse fühlt sich wie "schwanger" an. Ich denke, die Verwendung von Iterfaces 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. Man sollte Schnittstellen bevorzugen! Hinweis: Sie benötigen die Factory-Klasse nicht unbedingt. Relevant ist, dass die Klassenclients über die entsprechende Schnittstelle mit ihren Instanzen kommunizieren. Das Verstecken privater Methoden finde ich als seltsame Paranoia und sehe keinen Grund dafür, da wir Schnittstellen haben.
quelle
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:
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
zuerstsizeof(A)
Speicherbytes zu und übergibt dann demA::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:
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.
quelle
class A_impl *impl_;
Wir dürfen nicht vergessen, dass Vererbung eine stärkere und engere Kopplung ist als Delegation. Ich würde auch alle in den Antworten aufgeworfenen Fragen berücksichtigen, wenn ich entscheide, welche Design-Idiome zur Lösung eines bestimmten Problems verwendet werden sollen.
quelle
Obwohl in den anderen Antworten ausführlich darauf eingegangen, kann ich vielleicht etwas expliziter auf einen Vorteil von Pimpl gegenüber virtuellen Basisklassen eingehen:
Ein Pimpl-Ansatz ist aus Anwendersicht transparent, dh Sie können z. B. Objekte der Klasse auf dem Stapel erstellen und direkt in Containern verwenden. Wenn Sie versuchen, die Implementierung mithilfe einer abstrakten virtuellen Basisklasse auszublenden, müssen Sie einen gemeinsam genutzten Zeiger von einer Factory auf die Basisklasse zurückgeben, was die Verwendung erschwert. Betrachten Sie den folgenden äquivalenten Client-Code:
quelle
Nach meinem Verständnis dienen diese beiden Dinge völlig unterschiedlichen Zwecken. Der Zweck der Pickel-Redewendung besteht im Wesentlichen darin, Ihnen einen Überblick über Ihre Implementierung zu geben, damit Sie beispielsweise schnelle Swaps für eine Art durchführen können.
Der Zweck virtueller Klassen besteht eher darin, Polymorphismus zuzulassen, dh Sie haben einen unbekannten Zeiger auf ein Objekt eines abgeleiteten Typs, und wenn Sie die Funktion x aufrufen, erhalten Sie immer die richtige Funktion für jede Klasse, auf die der Basiszeiger tatsächlich zeigt.
Äpfel und Orangen wirklich.
quelle
Das nervigste Problem an der Pimpl-Sprache ist, dass es extrem schwierig ist, vorhandenen Code zu pflegen und zu analysieren. Wenn Sie also pimpl verwenden, zahlen Sie nur mit Entwicklerzeit und Frustration, um "Build-Abhängigkeiten und -Zeiten zu reduzieren und die Header-Exposition der Implementierungsdetails zu minimieren". Entscheide selbst, ob es sich wirklich lohnt.
Insbesondere "Build-Zeiten" sind ein Problem, das Sie durch bessere Hardware oder mithilfe von Tools wie Incredibuild (www.incredibuild.com, ebenfalls bereits in Visual Studio 2017 enthalten) lösen können, ohne Ihr Software-Design zu beeinträchtigen. Das Software-Design sollte im Allgemeinen unabhängig von der Art und Weise sein, wie die Software erstellt wird.
quelle