IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

pimpl et template


Sujet :

C++

  1. #1
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut pimpl et template
    Bonjour,

    imaginons que nous travaillons sur une bibliothèque. Nous voulons donc, idéalement, exporter un seul en-tête qui propose le protocole (ensemble des fonctions et variables publiques) et cacher l'implémentation en elle même.
    Pour ce faire, rien de tel qu'un classique pimpl:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /***** my_lib.h ********/
     
    class MyLibImpl;
     
    class MyLib
    {
    public:
     
    // protocol
       void DoSomething();
    // ...
     
    private:
       MyLibImpl * pimpl_; // pimpl: tout sera implémenté dans cette classe
       int a_super_important_value;
    };
     
    /***** my_lib.cpp ********/
    #include "my_lib.h"
    #include "my_lib_impl.h"
     
    void MyLib::DoSomething()
    {
       pimpl->DoSomething();
    }
    Vous noterez la présence de cette variable membre privée: a_super_important_value. Elle est type int.

    Donc imaginons que nous avons implémenté notre lib, que ça marche qu'on est prêt à lancer des tests en pré-prod. Et là, subitement, le commercial nous appelle et nous explique que finalement, cette super_important_value ne sera pas forcément un entier, mais d'un type indéterminé (donc pas forcément natif) que le client doit pouvoir choisir. Les commerciaux sont très forts, et dans cette situation hypothétique, nous nous sommes laissé convaincre, sans même s'en rendre compte, que c'était une bonne idée, et c'était pas bien grave de prévenir alors que le code était finit.

    Ok donc, pas de problème, grâce au c++ on peut tout faire. Pour ce problème là, on a les templates. Je vais transformer ma classe en template et ça va rouler tout seul:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /***** my_lib.h ********/
     
    class MyLibImpl;
     
    template <typename T>
    class MyLib
    {
    public:
     
    // protocol
       void DoSomething();
    // ...
     
    private:
       MyLibImpl * impl_; // pimpl: tout sera implémenté dans cette classe
       T a_super_important_value;
    };
    Sauf que non, on me fait signe que c'est pas possible! Et pour une raison toute bête: le code d'une classe template doit être dans l'en-tête (ou inclure le cpp à la fin de l'en-tête ce qui revient au même). Et donc ça casse mon pimpl.

    Savez-vous quelles sont les solutions pour se sortir de cette situation à peu de frais?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    De manière générale, ce qui importe dans pimpl, c'est l'interface de ta classe qui le gère... Le fait que le code qui permet l'appel des différentes fonctions soit dans un fichier cpp ou dans le fichier d'en-tête pour cause de besoin d'inlining ne pose pas vraiment de problème majeur.

    Le problème auquel tu pourrait éventuellement être confronté, c'est si tu t'apprêtes à fournir une bibliothèque dynamique et qu'il faut que tout le code correspondant soit bel et bien accessible depuis la bibliothèque.

    Dans ce cas, je partirais sans doute du principe que le type de a_super_important_value doit, quoi qu'il en soit, proposer une interface propre (sans doute proche de ce que l'on peut obtenir avec des valeurs numériques), et j'envisagerais l'utilisation de l'instanciation explicite pour "tout type connu correspondant" à cette interface, en gardant en tête le fait que cela correspondra à une augmentation sans doute substantielle de taille de la bibliothèque...
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par koala01 Voir le message
    De manière générale, ce qui importe dans pimpl, c'est l'interface de ta classe qui le gère... Le fait que le code qui permet l'appel des différentes fonctions soit dans un fichier cpp ou dans le fichier d'en-tête pour cause de besoin d'inlining ne pose pas vraiment de problème majeur.
    En fait non, ce qui importe dans mon cas, c'est que l'en-tête que je fournis (MyLib.h) ne doit inclure aucun autre fichier de ma lib. Car l'idée c'est de fournir au client le .lib/.a (ce sera une lib statique et pis c'est tout) et un seul et unique en-tête. Si MyLib.h inclus d'autres en-têtes, il faut alors fournir ces en-têtes également, et c'est ce que je souhaite éviter.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    La question est alors : de quels type est susceptible d'être a_super_important_value

    A priori, les types devraient ne pas être si nombreux que cela dans le sens où cette variable était prévue à l'origine pour être une valeur numérique entière.

    Je serais donc relativement surpris si tu devais prévoir beaucoup plus que les treize types primitifs et quelque chose ressemblant à std::complex.

    Dans de telles conditions, tu devrais pouvoir envisager l'instanciation explicite pour ces différents types et donc considérer les implémentations des différentes fonctions comme internes (en n'incluant le fichier qui les implémente que dans le fichier qui provoque l'instanciation explicite )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Sinon y'a peut-être une "astuce" crade pour éviter le template, qui est de mettre a_super_important_value en void*
    En fournissant qqs méthodes pour manipuler ça (une méthode template qui réalise le transtypage par exemple).

    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  6. #6
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par koala
    Dans de telles conditions, tu devrais pouvoir envisager l'instanciation explicite pour ces différents types
    Tu as dû louper 4 mots dans mon message initial:
    Savez-vous quelles sont les solutions pour se sortir de cette situation à peu de frais?


    Non mais plus sérieusement, le problème c'est qu'apparemment, ils veulent y injecter des objets à eux, et je n'ai pas la moindre idée de ce que ça peut être. La seule chose que je sais, c'est que ces objets implémenterons les opérateurs de bases (+,-,*,/) ainsi que les opérateurs de flux.

    Citation Envoyé par Bousk Voir le message
    Sinon y'a peut-être une "astuce" crade pour éviter le template, qui est de mettre a_super_important_value en void*
    En fournissant qqs méthodes pour manipuler ça (une méthode template qui réalise le transtypage par exemple).

    Herb Sutter dit que cette méthode c'est très beaucoup pas bien!
    Mais bon, j'ai peur que ce ne soit la seule solution malheureusement
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  7. #7
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Tu peux peut-être utiliser le triplet interface + template + static_assert (ou boost::concept, je crois)

    Puis définir une classe de Nombre et des wrappers directs pour les types de bases
    Il te suffirait d'avoir un pointeur de ce Nombre
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  8. #8
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    A mon avis, le problème se situe dans le cahier des charges plus que dans le code lui-même. Si on ne sait pas à l'avance quels types circulent dans notre API, c'est que le besoin du client n'est pas suffisamment bien défini, point.

  9. #9
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    En fait, tu dois

    1. separer tout ce qui est pas dependant du type en paramettre dans des types non-template (par exemple si l'implementation a pas besoin du type, tu peux garder un pointeur ca ira)
    2. avoir tout ce qui est dependant du type dans le header.

    Donc on peut pas te repondre clairement sans savoir si a_super_important_value est exploite dans toutes les methodes de MyLibImpl.

    Si oui, alors t'as pas d'autre choix que je tout ecrire en template.
    Si non, alors tu as une separation claire entre le code qui en a besoin et le reste. Mets le reste dans les cpp, et le code dependant du type dans le header.

  10. #10
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Déclarer a_super_important_value de type boost::any peut être une solution rapide à mettre en place.
    Le principal inconvénient est que l'utilisateur de la lib devra compiler avec boost.

  11. #11
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Déclarer a_super_important_value de type boost::any peut être une solution rapide à mettre en place.
    Le principal inconvénient est que l'utilisateur de la lib devra compiler avec boost.
    Et en priant pour que la lib et l'appli "hôte" soient compilées avec des compilos identiques de versions identiques...

  12. #12
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par cob59 Voir le message
    Et en priant pour que la lib et l'appli "hôte" soient compilées avec des compilos identiques de versions identiques...
    Tu peux expliquer ?

    Me semblais que le problème de compilos différents c'était juste au niveau du mangling ?

  13. #13
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Tu peux expliquer ?

    Me semblais que le problème de compilos différents c'était juste au niveau du mangling ?
    Si une app et une DLL s'échangent une std::string mais que l'une et l'autre l'implémentent différemment, il y aura forcément de la casse. Cf cette réponse de quelqu’un qui l'explique mieux que moi.

    En règle générale, une API "portable" ne peut exporter que des types primitifs, des pointeurs et des classes déclarées à même le header.

  14. #14
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Mais ici, sauf erreur de ma part, boost::any est défini entièrement dans des headers, ça ne devrait pas poser de problèmes ?
    (tant que la version de boost de l'hôte est >= à celle utilisée pour compiler la lib)

    Mais oui, boost::any n'est pas la solution idéale, on est d'accord.

  15. #15
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    Il faut garder à l'esprit que les headers ont pour seule vocation d'être inclus dans des .cpp, à savoir ceux de la DLL et ceux de l'appli client. Ces .cpp vont être compilés avec 2 unités de compilation différentes donc, bien que boost::any soit entièrement défini dans un header, rien ne garantit que le boost::any produit par le client sera compatible avec le boost::any lu par la DLL.

    Il y aurait une chance si boost::any était un type POD, ce qui n'est pas le cas.
    Il suffirait que boost::any appelle le moindre new/delete/malloc/realloc/free d'une part et d'autre de l'interface pour aboutir à un plantage.

  16. #16
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    @OP: Quel est la dépendance exacte des services fournit par le pimpl par rapport au template ? Il est envisageable d'isoler tout ce qui n'en dépend pas en ajoutant une indirection (héritage).

    Après la présence de services associés aux template imposent assez fortement de ne pouvoir totalement "cacher" l'implémentaiton. Cependant, c'est si grave que cà ?

    @Iradrille: Par rapport au point relevé par cob59, de manière général, la production d'une API C++ non "header only" et/ou non "extern C" ne produira pas une API totalement portable. C'est cependant à nuancer, il me semble que GCC produit un document relatif aux ABI qui permet d'assurer une compatibilité partiel (backward) et est suivit par d'autre compilateur.

    C'est d'ailleurs visible lors de l'utilisation de systèmes Linux, il est fréquent qu'une simple récupération d'une bibliothèque (binaire) via les dépots fonctionne avec le compilateur du système utilisé, alors qu'en toute rigeur il faudrait la recompiler pour le dit système.

  17. #17
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Boost any ou n'importe quoi tant que le type n'est pas connu par la bibliotheque, ce que je dis est valable.
    En fait je me suis une fois retrouve dans un cas similaire, j'ai lute pour sortir un max de code des template mais qui soient utilise par les template.

    Au final je m'en suis sorti en realisant que tout le code n'avait pas besoin de connaitre toutes les infos de type, du coup je me suis retrouve avec du code template qui faisait appel a du code non-template.

  18. #18
    Membre éprouvé
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2009
    Messages
    552
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mars 2009
    Messages : 552
    Points : 1 060
    Points
    1 060
    Par défaut
    Bonsoir,

    Entre la template et le boost::any, il y a peut-être une interface à mettre en place pour le type de "a_super_important_value"?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    // abstraite extensible par l'utilisateur
    class Value {
    public:
        // interface
        virtual int toInteger() const = 0 ;
        // probablement un clone, etc. 
    };
     
    // predefinie
    class Integer : public Value {
    public:
         virtual int toInteger() const { return _value; }
    private:
         int _value ;
    };
     
    // espece de pointeur intelligent sur Value (boost::any avec une interface), histoire de 
    // conserver une sémantique par valeur malgré le polymorphisme sur Value.
    class Variant {
    public:
        // constructeur avec value & co
        // méthode qui ne connaissent que "Value" (operator, ->, *, & co) 
    private:
        Value *_value ;
    };
    Et hop :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    class MyLibImpl;
     
    class MyLib
    {
    public:
     
    // protocol
       void DoSomething();
    // ...
     
    private:
       MyLibImpl * pimpl_; // pimpl: tout sera implémenté dans cette classe
       Variant a_super_important_value;
    };
    Tu n'accèdes qu'à l'interface de "Value", ton utilisateur peu faire un :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    // predefinie
    class MonType : public Value {
    public:
         // implementation de l'interface de Value
         virtual int toInteger() const {
               return "cela ne nous regarde pas" ;
         }
    private:
         // mes super variables membres ;
    };
    Là, tu devrais pouvoir garder pimpl sur MyLibImpl (chose qui a moins d'intérêt sur "Value" vu que ton utilisateur veut pouvoir s'intégrer dans le système).

    Tu t'en sors normalement à moindre frais en rajoutant de "a_super_important_value->toInteger()" dans MyLib.cpp.

    Non?

Discussions similaires

  1. [Templates] Quel système utilisez-vous ? Pourquoi ?
    Par narmataru dans le forum Bibliothèques et frameworks
    Réponses: 270
    Dernier message: 26/03/2011, 00h15
  2. Template XHTML
    Par Sylvain James dans le forum XSL/XSLT/XPATH
    Réponses: 14
    Dernier message: 16/06/2003, 21h45
  3. appliquer plusieurs templates
    Par Manu_Just dans le forum XSL/XSLT/XPATH
    Réponses: 7
    Dernier message: 04/04/2003, 16h26
  4. template match="node() mais pas text()"
    Par Manu_Just dans le forum XSL/XSLT/XPATH
    Réponses: 4
    Dernier message: 26/03/2003, 10h52
  5. [XSLT] template
    Par demo dans le forum XSL/XSLT/XPATH
    Réponses: 4
    Dernier message: 09/09/2002, 11h31

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo