Menu
Coddy logo textTech

Pimpl-Idiom

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 78 von 104.

Das Pimpl-Idiom (Pointer to Implementation) ist eine Technik, die die Implementierungsdetails einer Klasse verbirgt, indem sie in eine separate, vorwärts deklarierte Klasse verschoben werden. Dies reduziert Kompilierabhängigkeiten und hält private Member vor der Header-Datei wirklich verborgen.

Die Kernidee ist einfach: Anstatt private Member direkt in Ihrer Klasse zu deklarieren, deklarieren Sie einen Zeiger auf eine Implementierungsklasse, die nur in der Quelldatei definiert ist:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
    void doSomething();
    
private:
    class Impl;  // Vorwärtsdeklaration
    std::unique_ptr<Impl> pImpl;
};
// Widget.cpp
#include "Widget.h"
#include <iostream>

class Widget::Impl {
public:
    int data = 42;
    void process() { std::cout << "Processing: " << data << "\n"; }
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;

void Widget::doSomething() { pImpl->process(); }

Die Hauptvorteile sind die Compilation Firewall und die Binärkompatibilität. Wenn Sie die Klasse Impl ändern, muss nur die Quelldatei neu kompiliert werden – nicht jede Datei, die den Header einbindet. Dies beschleunigt die Build-Zeiten in großen Projekten erheblich.

Beachten Sie, dass der Destruktor in der Quelldatei definiert sein muss (selbst wenn er als Standard definiert wurde), da unique_ptr den vollständigen Typ von Impl benötigt, um ihn zu löschen. Dies ist eine häufige Falle bei der ersten Verwendung von Pimpl.

challenge icon

Aufgabe

Einfach

Lassen Sie uns einen sicheren Nachrichten-Handler unter Verwendung des Pimpl-Idioms erstellen, um die Details der Verschlüsselungsimplementierung aus der Header-Datei zu verbergen. Dies demonstriert, wie Pimpl eine Kompilierungs-Firewall erstellt – jeder, der Ihren Header einbindet, wird die internen Abläufe Ihrer Nachrichtenverarbeitung nicht sehen.

Sie werden Ihren Code über drei Dateien organisieren:

  • SecureMessage.h: Definieren Sie die öffentliche Schnittstelle für Ihre SecureMessage-Klasse.

    Ihre Klasse sollte eine vorwärts deklarierte Impl-Klasse und einen std::unique_ptr darauf enthalten. Die öffentliche Schnittstelle sollte Folgendes umfassen:

    • Einen Konstruktor, der eine const std::string& für die ursprüngliche Nachricht entgegennimmt
    • Einen Destruktor (muss hier deklariert und in der .cpp-Datei definiert werden)
    • Eine setKey(int key)-Methode zum Festlegen eines Verschlüsselungsschlüssels
    • Eine getEncrypted()-Methode, die die verschlüsselte Nachricht als std::string zurückgibt
    • Eine getOriginal()-Methode, die die ursprüngliche Nachricht zurückgibt

    Der Header sollte nur die öffentliche Schnittstelle zeigen – es sollten hier keine Implementierungsdetails darüber sichtbar sein, wie die Verschlüsselung funktioniert.

  • SecureMessage.cpp: Definieren Sie die verschachtelte Impl-Klasse und implementieren Sie alle Methoden.

    Ihre Impl-Klasse sollte die ursprüngliche Nachricht und den Verschlüsselungsschlüssel (Standardwert 0) speichern und die eigentliche Verschlüsselungslogik verarbeiten. Verwenden Sie eine einfache Caesar-Chiffre für die Verschlüsselung: Verschieben Sie jedes Zeichen um den Schlüsselwert. Zum Beispiel wird mit dem Schlüssel 3 aus 'a' ein 'd', 'z' wird zu 'c' umgebrochen.

    Die Verschlüsselung sollte nur Kleinbuchstaben (a-z) betreffen und alle anderen Zeichen unverändert lassen. Denken Sie daran, den Destruktor hier zu definieren (auch wenn er auf default gesetzt ist), da unique_ptr den vollständigen Impl-Typ benötigt.

  • main.cpp: Lesen Sie zwei Eingaben ein:
    1. Eine Nachrichtenzeichenfolge (kann Leerzeichen enthalten)
    2. Einen Verschlüsselungsschlüssel (Ganzzahl)

    Erstellen Sie ein SecureMessage-Objekt, setzen Sie den Schlüssel und zeigen Sie die Ergebnisse an:

    1. Geben Sie Original: gefolgt von der ursprünglichen Nachricht aus
    2. Geben Sie Encrypted: gefolgt von der verschlüsselten Nachricht aus

Zum Beispiel mit den Eingaben hello world und 3:

Original: hello world
Encrypted: khoor zruog

Mit den Eingaben xyz abc und 5:

Original: xyz abc
Encrypted: cde fgh

Beachten Sie, dass die Header-Datei nichts über die Implementierung der Caesar-Chiffre verrät – das ist die Stärke von Pimpl. Wenn Sie später zu einem anderen Verschlüsselungsalgorithmus wechseln würden, müsste nur SecureMessage.cpp neu kompiliert werden, nicht jedoch Dateien, die den Header einbinden.

Spickzettel

Das Pimpl-Idiom (Pointer to Implementation) verbirgt Implementierungsdetails, indem es diese in eine separate Klasse verschiebt, die nur in der Quelldatei definiert ist, wodurch eine Kompilierungs-Firewall entsteht.

Grundstruktur mit Vorwärtsdeklaration im Header:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();  // Muss im Header deklariert und in der .cpp definiert werden
    void doSomething();
    
private:
    class Impl;  // Vorwärtsdeklaration
    std::unique_ptr<Impl> pImpl;
};

Implementierung in der Quelldatei:

// Widget.cpp
#include "Widget.h"

class Widget::Impl {
public:
    int data = 42;
    void process() { /* Implementierung */ }
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;  // Muss in der .cpp definiert werden

void Widget::doSomething() { pImpl->process(); }

Wichtige Punkte:

  • Der Destruktor muss in der Quelldatei definiert werden (selbst wenn er auf "default" gesetzt ist), da unique_ptr den vollständigen Typ von Impl benötigt, um ihn zu löschen
  • Änderungen an Impl erfordern nur eine Neukompilierung der Quelldatei, nicht der Dateien, die den Header einbinden
  • Bietet eine Kompilierungs-Firewall und Binärkompatibilität

Probier es selbst

#include <iostream>
#include <string>
#include "SecureMessage.h"

int main() {
    // Lies die Nachricht (kann Leerzeichen enthalten)
    std::string message;
    std::getline(std::cin, message);
    
    // Lies den Verschlüsselungsschlüssel
    int key;
    std::cin >> key;
    
    // TODO: Erstelle ein SecureMessage-Objekt mit der Nachricht
    
    // TODO: Setze den Verschlüsselungsschlüssel
    
    // TODO: Gib "Original: " gefolgt von der ursprünglichen Nachricht aus
    
    // TODO: Gib "Encrypted: " gefolgt von der verschlüsselten Nachricht aus
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung