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

Langage C++ Discussion :

Classe "normale" ou classe template ? Et pis pourquoi les templates en fait ?


Sujet :

Langage C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut Classe "normale" ou classe template ? Et pis pourquoi les templates en fait ?
    Hello !

    Allez, chui fou : deux questions dans le même thread car la question 2 est une suite logique de la question 1 Pour ceux qui n'ont pas suivi mes précédentes aventures, j'apprends le C++ et je bosse sur MCU. Mon apprentissage m'amène vers les templates. J'ai lu plusieurs cours sur le sujet et j'ai compris (pas les variadics encore...) comment ça marche et à quoi ça sert. Le hic c'est que je vois à quoi ça sert mais je n'y vois pas beaucoup d'application vraiment cools. J'ai vu des trucs trop simples comme des fonctions min(), des trucs moins simples mais pas trop utiles (en tout cas pour moi) comme des conteneurs, et enfin des trucs tellement compliqués que j'ai rien compris.

    Ma première question concerne l'utilisation pour faire des classes. Voici un code exemple :
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    class ClassicDisplay
    {
    public:
        ClassicDisplay(unsigned int width, unsigned int height) :
                width_m(width), height_m(height_m)
        {}
     
        void draw()
        {
        }
     
        unsigned int width_m;
        unsigned int height_m;
    };
     
    template<unsigned int __width, unsigned int __height>
    class TemplateDisplay
    {
    public:
        void draw()
        {
        }
     
        unsigned int width = __width;
        unsigned int height = __height;
    };
     
    int main()
    {
        ClassicDisplay cd(320, 240);
        TemplateDisplay<320, 240> td;
     
        cd.draw();
        td.draw();
     
        return cd.width_m + td.height;
    }
    En regardant le code assembleur généré avec Compiler Explorer, je vois que la méthode avec un template génère moins de code, mais cela ne devient plus vrai dés que je fais une seconde instanciation du template car la méthode draw() se retrouve dupliquer. Mon exemple est très simpliste, peut-être trop... La question est donc "quel est l'intérêt d'utiliser des classes templates plutôt que des classes "normales" avec un constructeur ?

    Ma seconde question va être un peu en mode stackoverflow : pouvez-vous de me donner des exemples concrets (sans aller dans l'inbitable) d'utiliser des templates ?

    Merci d'avance !

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 074
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 074
    Points : 12 120
    Points
    12 120
    Par défaut
    Un template dont tu connais tous les types à la déclaration, cela ne présente aucun intérêt.

    Les template ont un axe de variation du code qui se base sur les types, exactement comme les fonctions qui ont un axe de variation selon la valeur des paramètres passés.

    Imagines que tu as une fonction donc le code pourrait aussi bien fonctionner sur des int ou des float.
    "min" est un bon exemple, sans template, il te faudra une fonction "minInt" et une fonction "MinFloat", c'est relou.

    Donc, la méthode simple, si t'es pas encore à l'aise, c'est de toujours commencer par des classes et fonctions "classiques", mais dès que tu vois qu'une de tes nouvelles fonctions ou nouvelles classes correspond à une ancienne avec juste le type qui change, tu "templatises" ton ancienne fonction ou classe et tu ne fais pas le copier-coller de celle-ci.

  3. #3
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Je ne fais pas d'embarqué donc nos cas d'utilisation peuvent diverger, mais la plus fréquente justification d'utilisation de template dans mon domaine (3D temps réel) c'est réunir les traitements communs.

    Considère une fonction mathématique, qui réalise une interpolation par exemple : le code sera rigoureusement identique pour un float, un double, un vecteur, un quaternion... pour peu que tous ces types implémentent les opérateurs mathématiques de base. En écrivant le traitement sous forme de template, tu ne gagneras rien en taille d'exécutable car le compilateur instanciera au final une version de la fonction pour chaque type utilisé. Mais tu auras ainsi une seule fonction à écrire, débugger, maintenir.

  4. #4
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Dans ton exemple de code, imagine ce qui arriverait si dans ta classe Display, tu devais avoir un tableau de pixels correspondant aux dimensions que tu as indiqué. Avec la première solution, pas trop le choix, tu vas devoir allouer dynamiquement ce tableau. Avec les templates, comme ces dimensions sont connues à la compilation, tu as la possibilité de les allouer directement sur la pile, sans faire d'allocation dynamique (bon, en l'occurrence, c'est probablement pas une bonne idée, mais imagine que ta classe n'est pas un display, mais un filtre linéaire défini par une matrice de convolution dont la taille dépend du voisinage à prendre en compte, mais reste assez petite...).

    Alors, certes, le code sera dupliqué à chaque instanciation avec des valeurs différentes. Mais cette duplication permet aussi qu'il soit optimisé selon ces valeurs... (et il y a des techniques pour éviter trop de duplications). Généralement (l'embarqué est peut-être une exception), on préfère avoir plusieurs versions de la même fonction si cette place accrue se traduit par des performances améliorées.

    Il y a plein d'autres exemples, j'ai essayé d'en trouver un qui allait dans la direction que tu avais ébauchée.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  5. #5
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    @Bktero : Si ton but est de faire des calculs à la compilation pour réduire la taille du code et pour augmenter la vitesse d'exécution, tu peux utiliser constexpr, apparu en C++11. Exemple :
    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
    27
    28
    29
    // Code compilé ici : http://coliru.stacked-crooked.com/
    // Version de g++ : 6.1.0
    // Commande : g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
     
    #include <array>
     
    class Rectangle
    {
    public:
        constexpr Rectangle(unsigned width, unsigned height) :
            m_width(width), m_height(height)
        {}
     
        constexpr unsigned area() const
        {
            return m_width*m_height;
        }
    private:
        unsigned m_width;
        unsigned m_height;
    };
     
    int main()
    {
        constexpr Rectangle rectangle(3, 5);
        constexpr unsigned area = rectangle.area(); // area est évaluable à la compilation...
        std::array<int, area> array_of_15_elements; // ...donc cette ligne compile.
        return 0;
    }
    Voici la doc de constexpr :
    http://en.cppreference.com/w/cpp/language/constexpr

    Le même genre de code en C++98 ressemblerait à ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<unsigned width, unsigned height>
    struct Rectangle
    {
        static const unsigned area = width*height;
    };
     
    int main()
    {
        Rectangle<3,5> rectangle;
        int array_of_15_elements[rectangle.area]; // Ça compile.
        return 0;
    }

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    @bacelar, @Matt_Houston, @JolyLoic : merci pour ces précisions. Je retiens que ce n'est pas vraiment de la peine de penser template si on n'a pas de variations sur les types. J'ai fait pas mal de Java où les interfaces sont reines. Du coup, quand tu veux faire un traitement générique, tu prend en paramètre des objets répondant à une interface et tu utilises les méthodes de l'interface. Quel serait l'intérêt en C++ de faire ça avec des templates plutôt qu'avec une classe d'interface, cad ne contenant que des méthodes virtuelles pures ? Si les opérateurs ne peuvent pas être virtuels purs (ce que je ne sais pas encore), je vois un l'utilité, mais sinon...

    @JolyLoic : J'avais pensé à cette idée de performance accrue en permettant au compilateur de faire des optimisations (a ma connaissance, il n'y a pas encore de JIT en C++ ^^), c'est d'entendre une confirmation.

    @Pyramidev : je n'ai pas encore regardé du côté de constexpr, je retiens l'idée !

    Je vais rajouter une nouvelle question : comment passer un template à une fonction ? Quel pourrait-être le prototype d'une fonction prenant un template comme paramètre formel, sachant que le type de l'argument va dépendre de l'instanciation du template ? Exemple :
    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
    27
    #include <iostream>
    #include <array>
     
    using namespace std;
     
    template<unsigned int __width, unsigned int __height>
    class Display
    {
    public:
        void draw()
        {
        }
     
        unsigned int width = __width;
        unsigned int height = __height;
    };
     
    void clear(Display display) // l'est pas content le compilo là !
    {
        cout << "Write " << (display.width + display.height) << " white pixels to clear the display";
    }
     
    int main()
    {
        Display<320, 240> d;
        clear(d);
    }
    EDIT : je crois avoir trouver la solution ^^
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<unsigned int __width, unsigned int __height>
    void clear(Display<__width, __height> display)
    {
        cout << "Write " << (display.width + display.height) << " white pixels to clear the display";
    }
    J'ai bon ?

  7. #7
    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
    Ce dont tu parles ressemble beaucoup à ce qu'on appelle politics, tu passes en paramètre template des classes qui permettent certaines actions.
    Par exemple si tu regardes les interfaces de la stl, prenons std::set, tu peux y passer un comparateur qui servira à trier les éléments.
    Souvent ce comportement peut être réalisé via membre type std::function maintenant. (si tu veux pouvoir le modifier à l'exécution par exemple c'est obligatoire)
    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.

  8. #8
    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, l'utilisation des template nous incite à changer de paradigme, et à passer au paradigme dit "générique", qui consiste -- en gros -- à se dire "je ne sais pas encore quel type de données je vais manipuler, mais je sais par contre comment je vais le faire", avec, dans l'idée, de laisser le compilateur travailler le plus possible.

    Et, justement, le fait de laisser le compilateur travailler le plus possible nous permet pas mal de choses, par exemple :
    • de nous assurer que seules les fonctions réellement utilisées (en connaissant tous les paramètres template dont elles ont besoin) seront effectivement présentes dans le code
    • de faire des vérifications à la compilation (qui reste le meilleur moment pour trouver le plus d'erreurs possible): pense, par exemple, au type_traits et à l'insruction static_assert
    • d'activer (ou de désactiver) certaines fonctionnalités en fonction du type de donnée (intéresse toi à std::enable_if pour le coup)
    • de calculer des constantes de compilation dont la valeur sera reprise "telle quelle" dans le code binaire (*)
    • Et bien d'autres choses encore...

    (cette liste ne se veut décidemment pas exhaustive, tu l'auras sans doute déjà compris )
    (*) A titre d'exemple, devine à quoi pourrait ressembler le code assembleur de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <int i>
    struct Factorielle{
        enum{value = i* Factorielle<i-1>::value};
    };
    template <>
    struct Factorielle<1>{
        enum{value = 1};
    };
    int main()
    {
     auto i =Factorielle<4>::value;
     auto j = Factorielle<5>::value;
    }
    Tu seras surpris, mais il se limite royalement à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    main:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR [rbp-4], 24
            mov     DWORD PTR [rbp-8], 120
            mov     eax, 0
            pop     rbp
            ret
    Sympa, non?
    Citation Envoyé par Bktero Voir le message
    Je vais rajouter une nouvelle question : comment passer un template à une fonction ? Quel pourrait-être le prototype d'une fonction prenant un template comme paramètre formel, sachant que le type de l'argument va dépendre de l'instanciation du template ? Exemple :
    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
    27
    #include <iostream>
    #include <array>
     
    using namespace std;
     
    template<unsigned int __width, unsigned int __height>
    class Display
    {
    public:
        void draw()
        {
        }
     
        unsigned int width = __width;
        unsigned int height = __height;
    };
     
    void clear(Display display) // l'est pas content le compilo là !
    {
        cout << "Write " << (display.width + display.height) << " white pixels to clear the display";
    }
     
    int main()
    {
        Display<320, 240> d;
        clear(d);
    }
    EDIT : je crois avoir trouver la solution ^^
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<unsigned int __width, unsigned int __height>
    void clear(Display<__width, __height> display)
    {
        cout << "Write " << (display.width + display.height) << " white pixels to clear the display";
    }
    J'ai bon ?
    Si tu évite le recours à la directive usign namespace std; qui mérite les pire tortures à ceux qui ont l'audace de l'utiliser et en transmettant ton display sous forme de référence (ben oui, autrement, clear agit sur une copie du display et non sur celui qui est utilisé comme argument lors de l'appel de la fonction), oui, tu as bon

    Bien sur, tu aurais toujours la possibilité (et sans doute intérêt dans le cas présent) de faire en sorte que ta fonction clear soit une fonction membre de la class Display... Car, après tout, c'est une fonction qui ne sera jamais appelée que ... sur le display que tu veux "nettoyer" et qui, dans ce contexte, sera forcément considéré comme "le display courant"
    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

  9. #9
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Vos renvois vers des éléments de la bibliothèque standard me font dire que je ne suis pas encore assez dans le jus pour pleinement appréhender la philosophie de la programmation générique. Je crois que je vais y aller au fur et à mesure, histoire de me mettre dans le bain progressivement.

    @koala01 : il est bien évident qu'une telle fonction clear() devrait être dans la classe ! L'exemple était un peu grossier ^^ Bien vu pour la référence, il faut vraiment que je pense à ça !!

    Quelqu'un a un commentaire sur ça :
    J'ai fait pas mal de Java où les interfaces sont reines. Du coup, quand tu veux faire un traitement générique, tu prend en paramètre des objets répondant à une interface et tu utilises les méthodes de l'interface. Quel serait l'intérêt en C++ de faire ça avec des templates plutôt qu'avec une classe d'interface, cad ne contenant que des méthodes virtuelles pures ?
    En réfléchissant, j'ai pensé à l'élimination des vtables puisque pas d'interface veut dire pas de fonctions virtuelles. Avez-vous d'autres choses ? Evitez la multiplication d'interfaces avec une ou deux méthodes (genre Comparable) ?

  10. #10
    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 Bktero Voir le message
    J'ai fait pas mal de Java où les interfaces sont reines.
    La notion d'interface n'existe pas en c++, du moins pas dans ce sens là. En C++, on parle parfois d'interface pour désigner l'ensemble des fonctions accessibles d'une classe (fonctions publiques + publiques héritées). Je préfère, en ce qui me concerne, parler de protocole. Mais quoi qu'il en soit, évite le mot 'interface' en c++. Car on peut simuler le comportement d'une interface java en ne déclarant que des fonctions membres virtuelles pures (et sans déclarer de variable membre), mais ça n'a aucun intérêt en c++.

    D'autant plus qu'il faut se méfier des fonctions vituelles pures publiques (voir NVI par exemple).

    Enfin, il faut garder à l'esprit qu'une hiérarchie de classe trop profonde n'est pas une bonne chose, car difficile à comprendre et à maintenir. L'utilisation de template peut donc aider à limiter cette profondeur, bien que ce ne soit évidemment pas le but initial.

    Citation Envoyé par Bktero Voir le message
    pas d'interface veut dire pas de fonctions virtuelles
    On peut très bien faire de l'héritage - donc utiliser des fonctions virtuelles - sans passer par des "java-type interface". C'est d'ailleurs ce qu'on fait en c++.
    « 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

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 074
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 074
    Points : 12 120
    Points
    12 120
    Par défaut
    Avez-vous d'autres choses ? Evitez la multiplication d'interfaces avec une ou deux méthodes (genre Comparable) ?
    Il y a un clivage de paradigme entre l'utilisation d'interfaces JAVA et les templates C++.

    Il ne faut pas chercher à savoir "pourquoi" les templates C++ et les interfaces JAVA sont différents.

    Historiquement, les templates C++ existaient avant JAVA.
    Les templates C++ sont extrêmement souple et puissant et absolument pas basés sur le moindre héritage.

    Au moment de la conception de JAVA, les templates C++ manquaient de normalisation et sont assez complexes à utiliser. Les concepteurs de JAVA n'ont donc pas voulu intégrer cette caractéristique C++, comme l'héritage multiple, etc...
    La présence d'un God Object et les interfaces en JAVA ont permis de faire des choses sympas mais assez différents des templates C++.

    On peut limiter l'utilisation des templates à ce que vous pouvez faites avec des interfaces JAVA, mais c'est clairement se brider.

    Si on prend le cas du template "min", la manière de l'implémenter pour un "int" ou un double "double" sont identiques, et faire dériver "int" et "double" d'une interface "IComparable", c'est impossible (et bien relou ).

  12. #12
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Merci à tous pour vos réponses. Il ne me reste plus qu'à m'imprégner de tout ça pour mieux cerner quand et comment utiliser les templates. J'ai des réflexes Java, je vais sûrement les garder encore un peu.

    C'est déjà entendu parler de NVI, l'idée est intéressante et formaliser très bien une façon de faire que j'ai déjà utilisé. C'est un peu à rapproche du pattern Stratégie, je trouve.

    Pour en finir avec mon usage des "interfaces" à la Java, j'ai par exemple quelques classes comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include "Events.h"
     
    class EventListener
    {
    public:
        virtual ~EventListener()
        {
        }
        virtual void handleButtonEvent(ButtonEvent event) = 0;
        virtual void handleSliderEvent(SliderEvent event) = 0;
    };
    Est-ce qu'il y aurait des alternatives à cela ? D'ailleurs, est-il nécessaire de trouver une alternative ? Je ne vois rien de mal dans ce code personnellement.

  13. #13
    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
    Citation Envoyé par Bktero Voir le message
    Je ne vois rien de mal dans ce code personnellement.
    Et à juste titre imo.
    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.

  14. #14
    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 Bktero Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #include "Events.h"
     
    class EventListener
    {
    public:
        virtual void handleButtonEvent(ButtonEvent event) = 0;
        virtual void handleSliderEvent(SliderEvent event) = 0;
    };
    Est-ce qu'il y aurait des alternatives à cela ? D'ailleurs, est-il nécessaire de trouver une alternative ? Je ne vois rien de mal dans ce code personnellement.
    Il y a plusieurs alternatives.
    La première et la plus simple, des fonctions avec un corps vide:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class EventListener
    {
    public:
        virtual ~EventListener()
        {
        }
        virtual void handleButtonEvent(ButtonEvent event) {}
        virtual void handleSliderEvent(SliderEvent event) {}
    };
    La seconde, qui n'est la bonne que dans certains cas particuliers, NVI:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class EventListener
    {
    public:
        void handleButtonEvent(ButtonEvent event) {handleButtonEvent_Impl(event);}
        void handleSliderEvent(SliderEvent event) {handleSliderEvent_Impl(event);}
     
    protected:
     
        virtual void handleButtonEvent_Impl(ButtonEvent event) = 0;
        virtual void handleSliderEvent_Impl(SliderEvent event) = 0;
    };
    NVI c'est pas fou, mais ça peut servir dans certains cas particuliers.
    En revanche, mettre un corps vide à la place d'une virtuelle pure, je crois que c'est une bonne pratique. Ça ne coûte rien, et ça facilite et simplifie la hiérarchie de classe (donc la maintenance etc.).
    « 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

  15. #15
    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
    Certes, mais seulement si cela à un sens.
    Et pour rendre une telle classe abstraite quand même, on peut rendre le destructeur virtuel pur.
    Cependant, il faudra quand même lui donner une définition hors de la classe
    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

  16. #16
    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
    Citation Envoyé par r0d Voir le message
    En revanche, mettre un corps vide à la place d'une virtuelle pure, je crois que c'est une bonne pratique. Ça ne coûte rien, et ça facilite et simplifie la hiérarchie de classe (donc la maintenance etc.).
    Voir, si la fonction doit renvoyer quelque chose, faire en sorte que le comportement par défaut renvoye une valeur nulle ou fausse.

    On pourrait très bien avoir quelque chose comme
    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
    class EventListener
    {
    public:
        /* @return true if button event has been handeled, false otherwhise */
        bool handleButtonEvent(ButtonEvent event) {return handleButtonEvent_Impl(event);}
        /* @return true if slider event has been handeled, false otherwhise */
        bool handleSliderEvent(SliderEvent event) {return handleSliderEvent_Impl(event);}
     
    protected:
        /* by default, we don't handle the event so that we only have to 
          * override this function for derived classes whose realy handle button events
          */
        virtual void handleButtonEvent_Impl(ButtonEvent event) { return false;}
     
        /* by default, we don't handle the event so that we only have to 
          * override this function for derived classes whose realy handle slider events
          */
        virtual void handleSliderEvent_Impl(SliderEvent event) {return false;}
    };
    Car il faut bien prendre conscience que toute classe dérivée pour laquelle il reste ne serait-ce qu'une fonction virtuelle pure sera forcément considérée comme... étant une classe abstraite.

    Du coup, le fait de déclarer une fonction comme étant virtuelle pure (que ce soit dans une application basique du principe de virtualité ou dans le cadre particulier du NVI) oblige le développeur de la classe dérivée à redéfinir le comportement de toutes les fonctions virtuelles pures

    En fournissant un comportement par défaut à toutes les fonctions virtuelles, nous permettons au développeur de la classe dérivée de n'avoir que... les comportements qui l'intéressent réellement à redéfinir. Ce qui facilite la tâche de tout le monde vu que, si une classe dérivée ne doit gérer que les événements associés à un bouton, son développeur aurait autrement aussi du implémenter le comportement de la fonction de gestion des événements associés aux sliders, et qu'il l'aurait donc selon toute vraisemblance fait sous la forme... du comportement par défaut.

    Cela pose néanmoins un problème inattendu : on ne peut plus forcément compter sur la présence d'une fonction virtuelle pure pour éviter l'instanciation de la classe de base (à moins de déclarer le destructeur de celle-ci comme virtuel pure, tout en en fournissant malgré tout une implémentation).

    Mais une autre solution pour y remédier est, tout simplement, de déclarer le constructeur de la classe de base dans l'accessibilité protégée. Cela rend de facto la classe de base non instanciable, tout en permettant aux classes dérivée d'y faire appel
    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

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Moteur de template ou PHP pour les templates
    Par pierrehs dans le forum Général Conception Web
    Réponses: 1
    Dernier message: 22/03/2011, 11h32
  2. [OpenTBS] Pourquoi les templates ?
    Par cedre22 dans le forum Bibliothèques et frameworks
    Réponses: 10
    Dernier message: 02/02/2006, 09h36

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