Idiome Pimpl
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 78 sur 104.
L'idiome Pimpl (Pointer to Implementation) est une technique qui masque les détails d'implémentation d'une classe en les déplaçant dans une classe distincte, déclarée au préalable. Cela réduit les dépendances de compilation et maintient les membres privés véritablement cachés du fichier d'en-tête.
L'idée centrale est simple : au lieu de déclarer des membres privés directement dans votre classe, vous déclarez un pointeur vers une classe d'implémentation qui n'est définie que dans le fichier source :
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // Déclaration anticipée
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(); }Les principaux avantages sont le pare-feu de compilation et la compatibilité binaire. Lorsque vous modifiez la classe Impl, seul le fichier source nécessite une recompilation - pas tous les fichiers qui incluent l'en-tête. Cela accélère considérablement les temps de compilation dans les grands projets.
Notez que le destructeur doit être défini dans le fichier source (même s'il est défini par défaut) car unique_ptr a besoin du type complet de Impl pour le supprimer. C'est un piège courant lors de la première utilisation de Pimpl.
Défi
FacileConstruisons un gestionnaire de messages sécurisés en utilisant l'idiome Pimpl pour masquer les détails d'implémentation du chiffrement dans le fichier d'en-tête. Cela démontre comment Pimpl crée un pare-feu de compilation — quiconque inclut votre en-tête ne verra pas le fonctionnement interne du traitement de vos messages.
Vous organiserez votre code sur trois fichiers :
SecureMessage.h: Définissez l'interface publique pour votre classeSecureMessage.Votre classe doit avoir une classe
Impldéclarée de manière anticipée (forward declaration) et unstd::unique_ptrpointant vers celle-ci. L'interface publique doit inclure :- Un constructeur qui prend une
const std::string&pour le message original - Un destructeur (doit être déclaré ici, défini dans le fichier .cpp)
- Une méthode
setKey(int key)pour définir une clé de chiffrement - Une méthode
getEncrypted()qui retourne le message chiffré sous forme destd::string - Une méthode
getOriginal()qui retourne le message original
L'en-tête ne doit montrer que l'interface publique — aucun détail d'implémentation sur le fonctionnement du chiffrement ne doit être visible ici.
- Un constructeur qui prend une
SecureMessage.cpp: Définissez la classe imbriquéeImplet implémentez toutes les méthodes.Votre classe
Impldoit stocker le message original, la clé de chiffrement (par défaut à 0), et gérer la logique de chiffrement réelle. Utilisez un simple chiffre de César pour le chiffrement : décalez chaque caractère de la valeur de la clé. Par exemple, avec une clé de 3, 'a' devient 'd', 'z' boucle pour devenir 'c'.Le chiffrement ne doit affecter que les lettres minuscules (a-z), en laissant tous les autres caractères inchangés. N'oubliez pas de définir le destructeur ici (même s'il est par défaut) car
unique_ptra besoin du typeImplcomplet.main.cpp: Lisez deux entrées :- Une chaîne de message (peut contenir des espaces)
- Une clé de chiffrement (entier)
Créez un objet
SecureMessage, définissez la clé et affichez les résultats :- Affichez
Original:suivi du message original - Affichez
Encrypted:suivi du message chiffré
Par exemple, avec les entrées hello world et 3 :
Original: hello world
Encrypted: khoor zruogAvec les entrées xyz abc et 5 :
Original: xyz abc
Encrypted: cde fghRemarquez comment le fichier d'en-tête ne révèle rien sur l'implémentation du chiffre de César — c'est là toute la puissance de Pimpl. Si vous changiez plus tard pour un algorithme de chiffrement différent, seul SecureMessage.cpp nécessiterait une recompilation, et non les fichiers qui incluent l'en-tête.
Aide-mémoire
L'idiome Pimpl (Pointer to Implementation) masque les détails d'implémentation en les déplaçant vers une classe distincte définie uniquement dans le fichier source, créant ainsi un pare-feu de compilation.
Structure de base avec déclaration anticipée dans l'en-tête :
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget(); // Doit être déclaré dans l'en-tête, défini dans le .cpp
void doSomething();
private:
class Impl; // Déclaration anticipée
std::unique_ptr<Impl> pImpl;
};Implémentation dans le fichier source :
// Widget.cpp
#include "Widget.h"
class Widget::Impl {
public:
int data = 42;
void process() { /* implémentation */ }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // Doit être défini dans le .cpp
void Widget::doSomething() { pImpl->process(); }Points clés :
- Le destructeur doit être défini dans le fichier source (même s'il est par défaut) car
unique_ptra besoin du type complet deImplpour le supprimer - Les modifications apportées à
Implne nécessitent que la recompilation du fichier source, et non des fichiers qui incluent l'en-tête - Fournit un pare-feu de compilation et une compatibilité binaire
Essayez vous-même
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// Lire le message (peut contenir des espaces)
std::string message;
std::getline(std::cin, message);
// Lire la clé de chiffrement
int key;
std::cin >> key;
// TODO: Créer un objet SecureMessage avec le message
// TODO: Définir la clé de chiffrement
// TODO: Afficher "Original: " suivi du message original
// TODO: Afficher "Encrypted: " suivi du message chiffré
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique