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ärtsdeklarierte Klasse verschoben werden. Dadurch werden Kompilierungsabhängigkeiten reduziert und private Member wirklich vor der Header-Datei verborgen.
Die grundlegende Idee ist einfach: Anstatt private Member direkt in deiner Klasse zu deklarieren, deklarierst du 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 wichtigsten Vorteile sind Kompilierungs-Firewall und Binärkompatibilität. Wenn du die Klasse Impl änderst, muss nur die Quelldatei neu kompiliert werden – nicht jede Datei, die den header einbindet. Dadurch werden die Build-Zeiten in großen Projekten drastisch verkürzt.
Beachte, dass der Destruktor in der Quelldatei definiert werden muss (selbst wenn er als Standardwert festgelegt ist), da unique_ptr den vollständigen Typ von Impl benötigt, um ihn zu löschen. Dies ist ein häufiges Problem bei der ersten Verwendung von Pimpl.
Aufgabe
EinfachErstellen wir einen sicheren Nachrichten-Handler mit dem Pimpl-Idiom, um die Details der Verschlüsselungsimplementierung vor der Header-Datei zu verbergen. Dies veranschaulicht, wie Pimpl eine Kompilierungs-Firewall erzeugt: Jeder, der Ihren Header einbindet, sieht nicht die internen Abläufe Ihrer Nachrichtenverarbeitung.
Sie werden Ihren Code auf drei Dateien verteilen:
SecureMessage.h: Definieren Sie die öffentliche Schnittstelle für IhreSecureMessage-Klasse.Ihre Klasse sollte eine vorwärtsdeklarierte
Impl-Klasse und einenstd::unique_ptrdarauf besitzen. Die öffentliche Schnittstelle sollte Folgendes enthalten:- 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 alsstd::stringzurückgibt - Eine
getOriginal()-Methode, die die ursprüngliche Nachricht zurückgibt
Der Header sollte nur die öffentliche Schnittstelle zeigen: Hier sollten keine Implementierungsdetails darüber sichtbar sein, wie die Verschlüsselung funktioniert.
- Einen Konstruktor, der eine
SecureMessage.cpp: Definieren Sie die verschachtelteImpl-Klasse und implementieren Sie alle Methoden.Ihre
Impl-Klasse sollte die ursprüngliche Nachricht, den Verschlüsselungsschlüssel (standardmäßig auf 0 gesetzt) speichern und die eigentliche Verschlüsselungslogik verarbeiten. Verwenden Sie zur Verschlüsselung eine einfache Caesar-Chiffre: Verschieben Sie jedes Zeichen um den Wert des Schlüssels. Bei einem Schlüssel von 3 wird beispielsweise „a“ zu „d“ und „z“ wird zu „c“ zurückgeführt.Die Verschlüsselung sollte nur Kleinbuchstaben (a-z) beeinflussen und alle anderen Zeichen unverändert lassen. Denken Sie daran, den Destruktor hier zu definieren (auch wenn er standardmäßig implementiert wird), da
unique_ptrden vollständigen TypImplbenötigt.main.cpp: Lesen Sie zwei Eingaben ein:- Eine Nachrichtenzeichenkette (kann Leerzeichen enthalten)
- Einen Verschlüsselungsschlüssel (Ganzzahl)
Erstellen Sie ein
SecureMessage-Objekt, legen Sie den Schlüssel fest und geben Sie die Ergebnisse aus:- Geben Sie
Original:gefolgt von der ursprünglichen Nachricht aus - Geben Sie
Encrypted:gefolgt von der verschlüsselten Nachricht aus
Bei den Eingaben hello world und 3 sieht das Ergebnis beispielsweise so aus:
Original: hello world
Encrypted: khoor zruogBei den Eingaben xyz abc und 5 sieht das Ergebnis so aus:
Original: xyz abc
Encrypted: cde fghBeachten 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, müsste nur SecureMessage.cpp neu kompiliert werden, nicht jede Datei, die den Header einbindet.
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 Originalnachricht aus
// TODO: Gib "Encrypted: " gefolgt von der verschlüsselten Nachricht aus
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins mittels CRTPPimpl-IdiomTyplöschungEnum-Klassen & starke TypisierungAusnahmebehandlung in OOPBenutzerdefinierte Ausnahmehierarchien3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler