Je lis le livre "Exceptional C ++" par Herb Sutter, et dans ce livre j'ai appris l'idiome pImpl. Fondamentalement, l'idée est de créer une structure pour les private
objets de a class
et de les allouer dynamiquement pour diminuer le temps de compilation (et aussi masquer les implémentations privées d'une meilleure manière).
Par exemple:
class X
{
private:
C c;
D d;
} ;
pourrait être changé en:
class X
{
private:
struct XImpl;
XImpl* pImpl;
};
et, dans le RPC, la définition:
struct X::XImpl
{
C c;
D d;
};
Cela semble assez intéressant, mais je n'ai jamais vu ce genre d'approche auparavant, ni dans les entreprises où j'ai travaillé, ni dans les projets open source dont j'ai vu le code source. Alors, je me demande si cette technique est vraiment utilisée dans la pratique?
Dois-je l'utiliser partout ou avec prudence? Et cette technique est-elle recommandée pour être utilisée dans les systèmes embarqués (où les performances sont très importantes)?
la source
struct XImpl : public X
. Cela me semble plus naturel. Y a-t-il un autre problème que j'ai manqué?const unique_ptr<XImpl>
plutôt queXImpl*
.Réponses:
Bien sûr, il est utilisé. Je l'utilise dans mon projet, dans presque toutes les classes.
Raisons d'utiliser l'idiome PIMPL:
Compatibilité binaire
Lorsque vous développez une bibliothèque, vous pouvez ajouter / modifier des champs à
XImpl
sans interrompre la compatibilité binaire avec votre client (ce qui signifierait des plantages!). Étant donné que la disposition binaire de laX
classe ne change pas lorsque vous ajoutez de nouveaux champs à laXimpl
classe, il est prudent d'ajouter de nouvelles fonctionnalités à la bibliothèque dans les mises à jour de versions mineures.Bien sûr, vous pouvez également ajouter de nouvelles méthodes non virtuelles publiques / privées à
X
/XImpl
sans rompre la compatibilité binaire, mais c'est à égalité avec la technique d'en-tête / d'implémentation standard.Masquage des données
Si vous développez une bibliothèque, en particulier une bibliothèque propriétaire, il peut être souhaitable de ne pas divulguer quelles autres bibliothèques / techniques d'implémentation ont été utilisées pour implémenter l'interface publique de votre bibliothèque. Soit à cause de problèmes de propriété intellectuelle, soit parce que vous pensez que les utilisateurs pourraient être tentés de prendre des hypothèses dangereuses sur l'implémentation ou simplement casser l'encapsulation en utilisant de terribles astuces de casting. PIMPL résout / atténue cela.
Temps de compilation
Le temps de compilation est réduit, car seul le fichier source (implémentation) de
X
doit être reconstruit lorsque vous ajoutez / supprimez des champs et / ou des méthodes auXImpl
classe (ce qui correspond à l'ajout de champs / méthodes privés dans la technique standard). En pratique, c'est une opération courante.Avec la technique d'en-tête / d'implémentation standard (sans PIMPL), lorsque vous ajoutez un nouveau champ à
X
, chaque client qui alloueX
(soit sur pile, soit sur tas) doit être recompilé, car il doit ajuster la taille de l'allocation. Eh bien, chaque client qui n'alloue jamais X doit également être recompilé, mais c'est juste une surcharge (le code résultant du côté client sera le même).De plus, avec la séparation standard en-tête / implémentation, il
XClient1.cpp
faut recompiler même lorsqu'une méthode privée aX::foo()
été ajoutéeX
etX.h
modifiée, même si elleXClient1.cpp
ne peut pas appeler cette méthode pour des raisons d'encapsulation! Comme ci-dessus, il s'agit d'une surcharge pure et liée au fonctionnement des systèmes de construction C ++ réels.Bien sûr, la recompilation n'est pas nécessaire lorsque vous modifiez simplement l'implémentation des méthodes (car vous ne touchez pas l'en-tête), mais c'est à égalité avec la technique d'en-tête / d'implémentation standard.
Cela dépend de la puissance de votre cible. Cependant, la seule réponse à cette question est: mesurez et évaluez ce que vous gagnez et perdez. De plus, sachez que si vous ne publiez pas une bibliothèque destinée à être utilisée dans des systèmes embarqués par vos clients, seul l'avantage du temps de compilation s'applique!
la source
Il semble que de nombreuses bibliothèques l'utilisent pour rester stable dans leur API, du moins pour certaines versions.
Mais comme pour toutes choses, vous ne devriez jamais utiliser quoi que ce soit partout sans prudence. Réfléchissez toujours avant de l'utiliser. Évaluez les avantages que cela vous procure et s'ils valent le prix que vous payez.
Les avantages que cela peut vous apporter sont:
Ceux-ci peuvent être ou non de réels avantages pour vous. Comme pour moi, je me fiche de quelques minutes de recompilation. Les utilisateurs finaux ne le font généralement pas non plus, car ils le compilent toujours une fois et depuis le début.
Les inconvénients possibles sont (également ici, selon l'implémentation et si ce sont de réels inconvénients pour vous):
Alors, donnez à tout une valeur et évaluez-la par vous-même. Pour moi, il s'avère presque toujours que l'utilisation de l'idiome pimpl ne vaut pas la peine. Il n'y a qu'un seul cas où je l'utilise personnellement (ou au moins quelque chose de similaire):
Mon wrapper C ++ pour l'
stat
appel linux . Ici, la structure de l'en-tête C peut être différente, en fonction de ce qui#defines
est défini. Et comme mon en-tête wrapper ne peut pas les contrôler tous, je ne les ai que#include <sys/stat.h>
dans mon.cxx
fichier et évite ces problèmes.la source
File
classe (qui expose une grande partie des informationsstat
renvoyées sous Unix) utilise la même interface sous Windows et Unix, par exemple.#ifdef
secondes pour rendre l'emballage aussi fin que possible. Mais chacun a des objectifs différents, l'important est de prendre le temps d'y réfléchir au lieu de suivre aveuglément quelque chose.D'accord avec tous les autres sur les produits, mais permettez-moi de mettre en évidence une limite: ne fonctionne pas bien avec les modèles .
La raison en est que l'instanciation du modèle nécessite la déclaration complète disponible là où l'instanciation a eu lieu. (Et c'est la principale raison pour laquelle vous ne voyez pas les méthodes de modèle définies dans les fichiers CPP)
Vous pouvez toujours faire référence à des sous-classes modélisées, mais comme vous devez toutes les inclure, tous les avantages du «découplage d'implémentation» lors de la compilation (en évitant d'inclure tout le code spécifique à la plate-forme partout, en raccourcissant la compilation) sont perdus.
C'est un bon paradigme pour la POO classique (basée sur l'héritage) mais pas pour la programmation générique (basée sur la spécialisation).
la source
D'autres personnes ont déjà fourni les avantages / inconvénients techniques, mais je pense que ce qui suit mérite d'être noté:
Avant tout, ne soyez pas dogmatique. Si pImpl fonctionne pour votre situation, utilisez-le - ne l'utilisez pas simplement parce que "c'est mieux OO car il cache vraiment l' implémentation" etc. Citant la FAQ C ++:
Juste pour vous donner un exemple de logiciel open source où il est utilisé et pourquoi: OpenThreads, la bibliothèque de threads utilisée par OpenSceneGraph . L'idée principale est de supprimer de l'en-tête (par exemple
<Thread.h>
) tout le code spécifique à la plate-forme, car les variables d'état internes (par exemple les poignées de thread) diffèrent d'une plate-forme à l'autre. De cette façon, on peut compiler du code contre votre bibliothèque sans aucune connaissance des particularités des autres plates-formes, car tout est caché.la source
Je considérerais principalement PIMPL pour les classes exposées pour être utilisées comme API par d'autres modules. Cela présente de nombreux avantages, car la recompilation des modifications apportées à l'implémentation PIMPL n'affecte pas le reste du projet. De plus, pour les classes d'API, elles favorisent une compatibilité binaire (les changements dans l'implémentation d'un module n'affectent pas les clients de ces modules, ils n'ont pas besoin d'être recompilés car la nouvelle implémentation a la même interface binaire - l'interface exposée par le PIMPL).
En ce qui concerne l'utilisation de PIMPL pour chaque classe, je considérerais la prudence car tous ces avantages ont un coût: un niveau supplémentaire d'indirection est nécessaire pour accéder aux méthodes d'implémentation.
la source
Je pense que c'est l'un des outils les plus fondamentaux pour le découplage.
J'utilisais pimpl (et de nombreux autres idiomes d'Exceptional C ++) sur un projet intégré (SetTopBox).
Le but particulier de cet idoim dans notre projet était de masquer les types utilisés par la classe XImpl. Plus précisément, nous l'avons utilisé pour masquer les détails des implémentations pour différents matériels, où différents en-têtes seraient extraits. Nous avions différentes implémentations de classes XImpl pour une plate-forme et différentes pour l'autre. La disposition de la classe X est restée la même quelle que soit la plate-forme.
la source
J'utilisais beaucoup cette technique dans le passé, mais je me suis ensuite retrouvé à m'en éloigner.
Bien sûr, il est judicieux de cacher les détails de l'implémentation aux utilisateurs de votre classe. Cependant, vous pouvez également le faire en faisant en sorte que les utilisateurs de la classe utilisent une interface abstraite et que le détail de l'implémentation soit la classe concrète.
Les avantages de pImpl sont:
En supposant qu'il n'y ait qu'une seule implémentation de cette interface, il est plus clair de ne pas utiliser de classe abstraite / implémentation concrète
Si vous avez une suite de classes (un module) telle que plusieurs classes accèdent au même "impl" mais que les utilisateurs du module n'utiliseront que les classes "exposées".
Pas de v-table si cela est supposé être une mauvaise chose.
Les inconvénients que j'ai trouvés de pImpl (où l'interface abstraite fonctionne mieux)
Bien que vous n'ayez qu'une seule implémentation «production», en utilisant une interface abstraite, vous pouvez également créer une implémentation «fictive» qui fonctionne dans les tests unitaires.
(Le plus gros problème). Avant les jours de unique_ptr et de déménagement, vous aviez des choix restreints quant à la façon de stocker le pImpl. Un pointeur brut et vous avez eu des problèmes avec votre classe non copiable. Un ancien auto_ptr ne fonctionnerait pas avec une classe déclarée avant (pas sur tous les compilateurs de toute façon). Les gens ont donc commencé à utiliser shared_ptr, ce qui était bien pour rendre votre classe copiable, mais bien sûr, les deux copies avaient le même shared_ptr sous-jacent auquel vous ne vous attendiez peut-être pas (modifiez-en une et les deux sont modifiées). Ainsi, la solution était souvent d'utiliser le pointeur brut pour le pointeur interne et de rendre la classe non copiable et de renvoyer un shared_ptr à la place. Donc deux appels à nouveau. (En fait, 3 étant donné l'ancien shared_ptr vous en ont donné un deuxième).
Techniquement pas vraiment correct car la constness n'est pas propagée à un pointeur de membre.
En général, je me suis donc éloigné au cours des années de pImpl pour passer à l'utilisation d'interface abstraite (et aux méthodes d'usine pour créer des instances).
la source
Comme beaucoup d'autres l'ont dit, l'idiome Pimpl permet d'atteindre une indépendance complète de dissimulation d'informations et de compilation, malheureusement avec le coût de la perte de performances (indirection de pointeur supplémentaire) et du besoin de mémoire supplémentaire (le pointeur de membre lui-même). Le coût supplémentaire peut être critique dans le développement de logiciels embarqués, en particulier dans les scénarios où la mémoire doit être économisée autant que possible. L'utilisation de classes abstraites C ++ comme interfaces conduirait aux mêmes avantages au même coût. Cela montre en fait une grande carence du C ++ où, sans se répéter aux interfaces de type C (méthodes globales avec un pointeur opaque comme paramètre), il n'est pas possible d'avoir une véritable indépendance de masquage et de compilation d'informations sans inconvénients de ressources supplémentaires: c'est principalement parce que le déclaration d'une classe, qui doit être incluse par ses utilisateurs,
la source
Voici un scénario réel que j'ai rencontré, où cet idiome a beaucoup aidé. J'ai récemment décidé de prendre en charge DirectX 11, ainsi que mon support DirectX 9 existant, dans un moteur de jeu. Le moteur intégrait déjà la plupart des fonctionnalités DX, donc aucune des interfaces DX n'a été utilisée directement; ils étaient simplement définis dans les en-têtes comme des membres privés. Le moteur utilise des DLL comme extensions, ajoutant la prise en charge du clavier, de la souris, du joystick et des scripts, comme semaine, comme de nombreuses autres extensions. Alors que la plupart de ces DLL n'utilisaient pas directement DX, elles nécessitaient des connaissances et un lien avec DX simplement parce qu'elles inséraient des en-têtes qui exposaient DX. En ajoutant DX 11, cette complexité allait augmenter considérablement, mais inutilement. Le déplacement des membres DX dans un Pimpl défini uniquement dans la source a éliminé cette imposition. En plus de cette réduction des dépendances de bibliothèque,
la source
Il est utilisé en pratique dans de nombreux projets. Son utilité dépend fortement du type de projet. L'un des projets les plus importants qui l'utilisent est Qt , où l'idée de base est de masquer l'implémentation ou le code spécifique à la plate-forme à l'utilisateur (d'autres développeurs utilisant Qt).
C'est une idée noble mais il y a un réel inconvénient à cela: le débogage Tant que le code caché dans les implémentations privées est de qualité supérieure, tout va bien, mais s'il y a des bogues là-dedans, alors l'utilisateur / développeur a un problème, parce que c'est juste un pointeur stupide vers une implémentation cachée, même s'il a le code source des implémentations.
Donc, comme dans presque toutes les décisions de conception, il y a des avantages et des inconvénients.
la source
Un avantage que je peux voir est qu'il permet au programmeur d'implémenter certaines opérations de manière assez rapide:
PS: J'espère que je ne comprends pas mal la sémantique des mouvements.
la source