Pourquoi faut-il utiliser l’idiome «PIMPL»?

Fiche d’information:

Le PIMPL Idiom (Pointer to IMPLementation) est une technique de masquage d’implémentation dans laquelle une classe publique encapsule une structure ou une classe invisible en dehors de la bibliothèque dont la classe publique fait partie.

Cela masque les détails d’implémentation internes et les données de l’utilisateur de la bibliothèque.

Lors de l’implémentation de cet idiome, pourquoi placeriez-vous les méthodes publiques sur la classe pimpl et non sur la classe publique puisque les implémentations de la méthode des classes publiques seraient compilées dans la bibliothèque et que l’utilisateur n’a que le fichier d’en-tête?

Pour illustrer cela, ce code met l’implémentation de Purr() sur la classe impl et l’enveloppe également.

Pourquoi ne pas implémenter Purr directement dans la classe publique?

 // header file: class Cat { private: class CatImpl; // Not defined here CatImpl *cat_; // Handle public: Cat(); // Constructor ~Cat(); // Destructor // Other operations... Purr(); }; // CPP file: #include "cat.h" class Cat::CatImpl { Purr(); ... // The actual implementation can be anything }; Cat::Cat() { cat_ = new CatImpl; } Cat::~Cat() { delete cat_; } Cat::Purr(){ cat_->Purr(); } CatImpl::Purr(){ printf("purrrrrr"); } 

    • Parce que vous voulez que Purr() puisse utiliser des membres privés de CatImpl . Cat::Purr() ne serait pas autorisé à un tel access sans déclaration d’ friend .
    • Parce que vous ne mélangez alors pas les responsabilités: une classe implémente, une classe vers l’avant.

    Je pense que la plupart des gens se réfèrent à ceci en tant qu’idiome de corps de poignée. Voir le livre de James Coplien Advanced C ++ Programming Styles and Idioms ( lien Amazon ). Il est également connu sous le nom de chat de Cheshire en raison du caractère de Lewis Caroll qui disparaît jusqu’à ce qu’il ne rest plus que le sourire.

    L’exemple de code doit être réparti sur deux ensembles de fichiers source. Seul le fichier Cat.h est le fichier fourni avec le produit.

    CatImpl.h est inclus par Cat.cpp et CatImpl.cpp contient l’implémentation de CatImpl :: Purr (). Cela ne sera pas visible pour le public en utilisant votre produit.

    Fondamentalement, l’idée est de cacher autant que possible la mise en œuvre des regards indiscrets. Ceci est très utile lorsque vous avez un produit commercial livré sous la forme d’une série de bibliothèques accessibles via une API avec laquelle le code du client est compilé et lié.

    Nous l’avons fait avec la réécriture du produit IONAs Orbix 3.3 en 2000.

    Comme mentionné par d’autres, l’utilisation de sa technique dissocie complètement l’implémentation de l’interface de l’object. Ensuite, vous ne devrez pas recomstackr tout ce qui utilise Cat si vous souhaitez simplement modifier l’implémentation de Purr ().

    Cette technique est utilisée dans une méthodologie appelée conception par contrat .

    Pour ce qui vaut la peine, il sépare l’implémentation de l’interface. Ce n’est généralement pas très important dans les projets de petite taille. Mais, dans les grands projets et les bibliothèques, il peut être utilisé pour réduire les temps de construction de manière significative.

    Considérer que l’implémentation de Cat peut inclure de nombreux en-têtes, peut impliquer une méta-programmation de modèle qui prend du temps à être compilée seule. Pourquoi un utilisateur qui veut simplement utiliser le Cat doit-il inclure tout cela? Par conséquent, tous les fichiers nécessaires sont cachés en utilisant l’idiome pimpl (d’où la déclaration en CatImpl de CatImpl ), et l’utilisation de l’interface ne force pas l’utilisateur à les inclure.

    Je développe une bibliothèque pour l’optimisation non linéaire (lisez “beaucoup de maths méchant”), qui est implémentée dans des modèles, donc la majeure partie du code est dans les en-têtes. Il faut environ cinq minutes pour comstackr (sur un processeur multi-core décent), et il suffit d’environ une minute pour parsingr les en-têtes dans un .cpp vide. Ainsi, toute personne utilisant la bibliothèque doit attendre quelques minutes chaque fois qu’elle comstack son code, ce qui rend le développement assez fastidieux . Cependant, en masquant l’implémentation et les en-têtes, il suffit d’inclure un simple fichier d’interface, qui comstack instantanément.

    Cela n’a rien à voir avec la protection de l’implémentation contre la copie par d’autres entresockets – ce qui ne se produirait probablement pas de toute façon, à moins que le fonctionnement interne de votre algorithme puisse être deviné à partir des définitions des variables membres (si c’est le cas probablement pas très compliqué et ne mérite pas d’être protégé en premier lieu.

    Si votre classe utilise l’idiome pimpl, vous pouvez éviter de modifier le fichier d’en-tête de la classe publique.

    Cela vous permet d’append / de supprimer des méthodes à la classe pimpl sans modifier le fichier d’en-tête de la classe externe. Vous pouvez également append / supprimer #includes au pimpl aussi.

    Lorsque vous modifiez le fichier d’en-tête de la classe externe, vous devez recomstackr tout ce qui l’inclut (et si l’un d’entre eux est un fichier d’en-tête, vous devez recomstackr tout ce qui les inclut, etc.).

    En règle générale, la seule référence à la classe Pimpl dans l’en-tête de la classe Owner (Cat dans ce cas) serait une déclaration directe, comme vous l’avez fait ici, car cela peut réduire considérablement les dépendances.

    Par exemple, si votre classe Pimpl a ComplicatedClass en tant que membre (et pas seulement un pointeur ou une référence à celle-ci), vous devrez alors avoir ComplicatedClass entièrement défini avant son utilisation. En pratique, cela signifie notamment “ComplicatedClass.h” (qui inclura indirectement tout ce dont dépend ComplicatedClass). Cela peut conduire à un remplissage de plusieurs en-têtes, ce qui est mauvais pour gérer vos dépendances (et vos temps de compilation).

    Lorsque vous utilisez l’idion pimpl, il vous suffit d’inclure les éléments utilisés dans l’interface publique de votre type Propriétaire (ce qui serait le cas ici). Ce qui rend les choses meilleures pour les utilisateurs de votre bibliothèque et signifie que vous n’avez pas à vous soucier des gens en fonction de certaines parties internes de votre bibliothèque – soit par erreur, soit parce qu’ils veulent faire quelque chose que vous n’autorisez pas. public privé avant d’inclure vos fichiers.

    Si c’est une classe simple, il n’y a généralement aucune raison d’utiliser un Pimpl, mais pour les moments où les types sont assez gros, cela peut être d’une grande aide (en particulier pour éviter les temps de construction longs).

    Eh bien, je ne l’utiliserais pas. J’ai une meilleure alternative:

    foo.h:

     class Foo { public: virtual ~Foo() { } virtual void someMethod() = 0; // This "replaces" the constructor static Foo *create(); } 

    foo.cpp:

     namespace { class FooImpl: virtual public Foo { public: void someMethod() { //.... } }; } Foo *Foo::create() { return new FooImpl; } 

    Ce motif a-t-il un nom?

    En tant que programmeur Python et Java, cela me plaît beaucoup plus que le langage pImpl.

    Placer l’appel à l’impl-> Purr dans le fichier cpp signifie qu’à l’avenir, vous pourriez faire quelque chose de complètement différent sans avoir à changer le fichier d’en-tête. Peut-être que l’année prochaine, ils découvriront une méthode d’assistance qu’ils auraient pu appeler à la place et qu’ils pourront changer le code pour appeler cela directement et ne pas utiliser impl-> Purr du tout. (Oui, ils pourraient obtenir la même chose en mettant à jour la méthode impl :: implicite de Purr, mais dans ce cas, vous êtes bloqué avec un appel de fonction supplémentaire qui n’obtient que la fonction suivante)

    Cela signifie également que l’en-tête n’a que des définitions et qu’il n’ya pas d’implémentation permettant une séparation plus nette, ce qui est le but de l’idiome.

    Je viens de mettre en place ma première classe de pimpl au cours des derniers jours. Je l’ai utilisé pour éliminer les problèmes que j’avais, y compris winsock2.h dans Borland Builder. Il semblait que l’alignement de la structure fût vicié et comme je possédais des éléments de socket dans les données privées de la classe, ces problèmes se propageaient à tous les fichiers cpp contenant l’en-tête.

    En utilisant pimpl, winsock2.h était inclus dans un seul fichier cpp où je pouvais mettre un couvercle sur le problème et ne pas m’inquiéter qu’il reviendrait me mordre.

    Pour répondre à la question initiale, l’avantage que j’ai trouvé lors de la transmission des appels à la classe pimpl était que la classe pimpl était la même que celle que vous aviez avant de l’impliquer. cours de façon bizarre. Il est beaucoup plus clair d’implémenter les publics pour simplement envoyer à la classe pimpl.

    Comme l’a dit M. Nodet, une classe, une responsabilité.

    Nous utilisons l’idiome PIMPL pour émuler la programmation orientée aspect où les aspects pré, post et error sont appelés avant et après l’exécution d’une fonction membre.

     struct Omg{ void purr(){ cout<< "purr\n"; } }; struct Lol{ Omg* omg; /*...*/ void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } } }; 

    Nous utilisons également un pointeur sur la classe de base pour partager différents aspects entre plusieurs classes.

    L’inconvénient de cette approche est que l’utilisateur de la bibliothèque doit prendre en compte tous les aspects qui vont être exécutés, mais ne voit que sa classe. Cela nécessite de parcourir la documentation pour tout effet secondaire.

    Je ne sais pas si c’est une différence qui mérite d’être mentionnée mais …

    Serait-il possible d’avoir l’implémentation dans son propre espace de nommage et d’avoir un espace de nommage public de wrapper / library pour le code que l’utilisateur voit:

     catlib::Cat::Purr(){ cat_->Purr(); } cat::Cat::Purr(){ printf("purrrrrr"); } 

    De cette façon, tout le code de la bibliothèque peut utiliser l’espace de noms cat et à mesure que le besoin d’exposer une classe à l’utilisateur survient, un wrapper peut être créé dans l’espace de noms catlib.

    Je trouve cela révélateur que, malgré la notoriété de l’idiome pimpl, je ne le vois pas très souvent dans la vie réelle (par exemple dans les projets open source).

    Je me demande souvent si les “avantages” sont exagérés; oui, vous pouvez masquer certains détails de votre implémentation, et oui, vous pouvez changer votre implémentation sans changer d’en-tête, mais il n’est pas évident que ce soient de gros avantages dans la réalité.

    C’est-à-dire qu’il n’est pas évident que votre implémentation soit bien cachée, et il est peut-être assez rare que les gens ne modifient vraiment que l’implémentation; dès que vous avez besoin d’append de nouvelles méthodes, par exemple, vous devez changer d’en-tête de toute façon.