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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    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 confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 502
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : Février 2005
    Messages : 5 502
    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
    Membre Expert
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    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 : 50
    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
    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
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    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
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    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 152
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 152
    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
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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

+ 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