Le Parefeu de compilation (compilation firewall, pimpl idiom)

Quand on déclare une classe dans un fichier d’en-tête, on doit fournir l’intégralité de ses membres, y compris les membres privés. Ces membres privés peuvent être modifiés à tout moment, en fonction des mécanisme internes à la classe, et il est dommage de devoir recompiler l’intégralité d’un projet parce qu’on a changé les détails d’implantation interne à une classe.

Le procédé du parefeu de compilation propose de sortir les détails d’implantation d’une classe du fichier d’en-tête pour éliminer cette contrainte, au prix d’une légère perte de performances et de mémoire, sensible pour des petits objets d’usage intensif.

// Machin.h, avant class Machin { public: Machin(); virtual ~Machin(); void PublicFunction(); private: int Data_; char* Name_; void PrivateFunction_(); };

On réduit la partie privée à un pointeur vers un objet d’une classe distincte, dont on n’a besoin de connaître que le nom :

// Machin.h, après class Machin { public: Machin(); virtual ~Machin(); void PublicFunction(); private: class Impl; Impl* pImpl_; };

Dans le fichier source de la classe (ou dans un fichier séparé, mais inclus uniquement dans ledit fichier source), on donne les détails de cette classe interne :

class Machin::Impl { friend class Machin; private: Impl(); ~Impl(); int Data_; char* Name_; void PrivateFunction_(); };

Les constructeurs de la classe englobante doivent instancier un objet interne ; bien entendu, on peut passer des paramètres.

Machin::Machin() { pImpl_ = new Impl(); }

Le destructeur de la classe englobante doit évidemment le libérer :

Machin::~Machin() { delete pImpl_; }

Si des fonctions de la classe interne ont besoin d’appeler des fonctions de la classe externe, prévoir un pointeur ou une référence vers cette classe externe comme donnée membre de la classe interne et paramètre de son constructeur, et lui passer this (ou *this).

class Machin::Impl { friend class Machin; private: Impl(Machin& Daddy) : Owner_(Daddy) {} ~Impl(); Machin& Owner_; int Data_; char* Name_; void PrivateFunction_(); }; Machin::Machin() { pImpl_ = new Impl(*this); } // Exemple d’appel privé vers public void Machin::Impl::PrivateFunction_() { Owner_.PublicFunction(); } // Exemple d’appel public vers privé void Machin::PublicFunction() { pImpl_->PrivateFunction_(); }

Voilà. Maintenant, vous pouvez remplacer int Data_ par long Data_ sans recompiler tout votre projet.