it-swarm.com.de

Was ist der Sinn des PImpl-Musters, während wir die Schnittstelle in C ++ für denselben Zweck verwenden können?

Ich sehe viel Quellcode, der PImpl-Idiom in C++ verwendet. Ich gehe davon aus, dass der Zweck darin besteht, die privaten Daten/Typen/Implementierungen auszublenden, um die Abhängigkeit zu beseitigen und dann die Kompilierungszeit und das Problem mit dem Header-Include zu reduzieren.

Schnittstellen-/rein abstrakte Klassen in C++ verfügen jedoch auch über diese Funktion. Sie können auch zum Ausblenden von Daten/Typ/Implementierung verwendet werden. Damit der Aufrufer beim Erstellen eines Objekts nur die Schnittstelle sehen kann, können wir im Header der Schnittstelle eine Factory-Methode deklarieren.

Der Vergleich ist:

  1. Kosten:

    Die Kosten für die Schnittstellenmethode sind niedriger, da Sie nicht einmal die Implementierung der öffentlichen Wrapper-Funktion void Bar::doWork() { return m_impl->doWork(); } wiederholen müssen, sondern nur die Signatur in der Schnittstelle definieren müssen.

  2. gut verstanden:

    Die Schnittstellentechnologie wird von jedem C++ - Entwickler besser verstanden.

  3. Leistung:

    Die Leistung der Schnittstelle ist nicht schlechter als die PImpl-Sprache, beides ein zusätzlicher Speicherzugriff. Ich gehe davon aus, dass die Leistung gleich ist.

Das Folgende ist der Pseudocode, um meine Frage zu veranschaulichen:

// Forward declaration can help you avoid include BarImpl header, and those included in BarImpl header.
class BarImpl;
class Bar
{
public:
    // public functions
    void doWork();
private:
    // You don't need to compile Bar.cpp after changing the implementation in BarImpl.cpp
    BarImpl* m_impl;
};

Der gleiche Zweck kann über die Schnittstelle implementiert werden:

// Bar.h
class IBar
{
public:
    virtual ~IBar(){}
    // public functions
    virtual void doWork() = 0;
};

// to only expose the interface instead of class name to caller
IBar* createObject();

Was ist der Sinn von PImpl?

49
ZijingWu

Erstens wird PImpl normalerweise für nicht polymorphe Klassen verwendet. Und wenn eine polymorphe Klasse PImpl hat, bleibt sie normalerweise polymorph, dh sie implementiert immer noch Schnittstellen und überschreibt virtuelle Methoden aus der Basisklasse und so weiter. Die einfachere Implementierung von PImpl ist also keine Schnittstelle, sondern eine einfache Klasse, die die Mitglieder direkt enthält!

Es gibt drei Gründe, PImpl zu verwenden:

  1. Die binäre Schnittstelle (ABI) unabhängig von den privaten Mitgliedern machen. Es ist möglich, eine gemeinsam genutzte Bibliothek zu aktualisieren, ohne den abhängigen Code neu zu kompilieren, jedoch nur, solange die binäre Schnittstelle dieselbe bleibt. Jetzt ändert fast jede Änderung im Header, außer dem Hinzufügen einer Nicht-Mitgliedsfunktion und dem Hinzufügen einer nicht-virtuellen Mitgliedsfunktion, den ABI. Das PImpl-Idiom verschiebt die Definition der privaten Mitglieder in die Quelle und entkoppelt somit den ABI von ihrer Definition. Siehe Fragile Binary Interface Problem

  2. Wenn sich ein Header ändert, müssen alle Quellen einschließlich des Headers neu kompiliert werden. Und die C++ - Kompilierung ist ziemlich langsam. Durch das Verschieben von Definitionen der privaten Mitglieder in die Quelle reduziert das PImpl-Idiom die Kompilierungszeit, da weniger Abhängigkeiten in den Header gezogen werden müssen, und die Kompilierungszeit nach Änderungen noch weiter, da die abhängigen Elemente nicht neu kompiliert werden müssen (ok, dies gilt auch für die Schnittstelle + Factory-Funktion mit versteckter Betonklasse).

  3. Für viele Klassen in C++ ist die Ausnahmesicherheit eine wichtige Eigenschaft. Oft müssen Sie mehrere Klassen in einer zusammenstellen, damit bei einem Vorgang mit mehr als einem Element keines der Mitglieder geändert wird oder wenn Sie eine Operation haben, die das Mitglied in einem inkonsistenten Zustand belässt, wenn es ausgelöst wird und das enthaltende Objekt erhalten bleiben muss konsistent. In diesem Fall implementieren Sie die Operation, indem Sie eine neue Instanz des PImpl erstellen und diese austauschen, wenn die Operation erfolgreich ist.

Tatsächlich kann die Schnittstelle auch nur zum Ausblenden der Implementierung verwendet werden, hat jedoch folgende Nachteile:

  1. Durch das Hinzufügen einer nicht virtuellen Methode wird ABI nicht beschädigt, durch Hinzufügen einer virtuellen Methode jedoch. Schnittstellen erlauben daher überhaupt kein Hinzufügen von Methoden, PImpl tut dies.

  2. Inteface kann nur über Zeiger/Referenz verwendet werden, daher muss der Benutzer für eine ordnungsgemäße Ressourcenverwaltung sorgen. Andererseits sind Klassen, die PImpl verwenden, immer noch Werttypen und behandeln die Ressourcen intern.

  3. Versteckte Implementierung kann nicht vererbt werden, Klasse mit PImpl kann.

Und natürlich hilft die Benutzeroberfläche nicht bei der Ausnahmesicherheit. Dazu benötigen Sie die Indirektion innerhalb der Klasse.

51
Jan Hudec

Ich möchte nur auf Ihren Leistungspunkt eingehen. Wenn Sie eine Schnittstelle verwenden, müssen Sie unbedingt virtuelle Funktionen erstellt haben, die vom Optimierer des Compilers NICHT eingebunden werden. Die PIMPL-Funktionen können (und werden wahrscheinlich, weil sie so kurz sind) eingebunden werden (möglicherweise mehr als einmal, wenn die IMPL-Funktion ebenfalls klein ist). Ein virtueller Funktionsaufruf kann nur optimiert werden, wenn Sie vollständige Programmanalyseoptimierungen verwenden, deren Ausführung sehr lange dauert.

Wenn Ihre PIMPL-Klasse nicht leistungskritisch verwendet wird, spielt dieser Punkt keine große Rolle, aber Ihre Annahme, dass die Leistung gleich ist, gilt nur in einigen Situationen, nicht in allen.

8
Chewy Gumball

Diese Seite beantwortet Ihre Frage. Viele Menschen stimmen Ihrer Behauptung zu. Die Verwendung von Pimpl bietet gegenüber privaten Feldern/Mitgliedern die folgenden Vorteile.

  1. Das Ändern privater Mitgliedsvariablen einer Klasse erfordert keine Neukompilierung von Klassen, die davon abhängen. Dadurch werden die Zeiten schneller und das FragileBinaryInterfaceProblem wird reduziert.
  2. Die Header-Datei muss keine # Klassen einschließen, die in privaten Mitgliedsvariablen 'by value' verwendet werden, sodass die Kompilierungszeiten schneller sind.

Das Pimpl-Idiom ist eine Optimierung zur Kompilierungszeit, eine Technik zum Aufbrechen von Abhängigkeiten. Es reduziert große Header-Dateien. Es reduziert Ihren Header nur auf die öffentliche Schnittstelle. Die Headerlänge ist in C++ wichtig, wenn Sie darüber nachdenken, wie es funktioniert. #include verkettet die Header-Datei effektiv mit der Quelldatei. Denken Sie also daran, dass vorverarbeitete C++ - Einheiten sehr groß sein können. Die Verwendung von Pimpl kann die Kompilierungszeiten verbessern.

Es gibt andere bessere Techniken zum Aufbrechen von Abhängigkeiten, bei denen Sie Ihr Design verbessern müssen. Was bedeuten die privaten Methoden? Was ist die einzige Verantwortung? Sollten sie eine andere Klasse sein?

5
Dave Hillier