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 :

Fonction Générique - Template ou Héritage


Sujet :

C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2008
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2008
    Messages : 89
    Points : 50
    Points
    50
    Par défaut Fonction Générique - Template ou Héritage
    Bonjour,
    J’ai une fonction – disons foo - qui va travailler sur des Points, qui peuvent être en 2D (p2D=(x,y)) ou en 3D (p3D=(x,y,z)). Disons que foo(Point, t) va multiplier chaque coordonnée par t. Y a-t-il un moyen de rendre foo générique (en utilisant les templates ?) pour qu’elle puisse travailler sur p2D ou p3D sans que l'on ai à écrire une fonction foo_2D (Point_2D, t) et une fonction foo_3D (Point_3D, t)?
    Christian

  2. #2
    Membre actif Avatar de Rewpparo
    Homme Profil pro
    Amateur
    Inscrit en
    Décembre 2005
    Messages
    170
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Amateur

    Informations forums :
    Inscription : Décembre 2005
    Messages : 170
    Points : 281
    Points
    281
    Par défaut
    2 solutions avec les templates

    La première c'est de faire de tes points des classes templatées
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <unsigned D> class Point
    {
       float m_coords[D];
    public:
       ...
    };
    et de faire ta fonction ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <unsigned D> void foo(Point<D>& p_point)
    {
      // Version générique pour n'importe quel D
    }

    2eme solution : spécialisation de template sur foo
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename T> void foo(T& p_point);
     
    template <> void foo(P2D& p_point)
    {
      // (Version 2D)
    }
    template <> void foo(P3D& p_point)
    {
      // (Version 3D)
    }
    Personellement j'utilise la première pour toute mon algèbre linéaire, ca marche plutot bien.
    Par contre je ne sais pas comment le compilateur réagira à la deuxième si tu fais foo(int) par exemple, vu que la déclaration laisse penser que ca marche, mais il n'y a pas d'implémentation.

  3. #3
    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,

    Je ne vois pas l'intérêt de faire de la template spécialisée dans la deuxième solution.

    Vous pouvez tout simplement faire ceci, les signatures des fonctions seront différentes :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    void foo( Point2D & p )
    {
     
    }
    void foo( Point3D & p )
    {
     
    }
    Note : C'est valable aussi pour les méthodes d'une même classe et c'est les choses du style "ma_transformation" qui se retrouveront en hiérarchie ou en template.

    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
     
     
    struct ma_transformation {
    	//...
     
    	void apply( Point2D & p ){
    		//...
    	}
     
    	void apply( Point3D & p ){
    		//...
    	}
     
    	//...
    };

  4. #4
    Membre actif Avatar de Rewpparo
    Homme Profil pro
    Amateur
    Inscrit en
    Décembre 2005
    Messages
    170
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Amateur

    Informations forums :
    Inscription : Décembre 2005
    Messages : 170
    Points : 281
    Points
    281
    Par défaut
    Tu as raison, j'ai pas bien réfléchi. Ca permet juste de pouvoir rajouter des implémentations sans rajouter un prototype, mais l'intéret est probablement minime dans ce cas précis.
    Mais ca fait parler spécialisation de template, ce qui n'est jamais une mauvaise chose

  5. #5
    Membre expérimenté Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Points : 1 396
    Points
    1 396
    Par défaut
    Perso j'aurais plutôt fait ça :

    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
    template <unsigned D> class Point
    {
       float m_coords[D];
    public:
       static const unsigned nb_dim;
       ...
    };
     
    template <unsigned D> 
    const unsigned Point<D>::nb_dim = D;
     
    template <typename T> void foo(T& point, int t)
    {
      for(int i=0; i < T::nb_dim; ++i)
      // ...
    }
    Et au lieu de faire de foo une fonction à part, j'aurais surcharger l'opérateur '*' pour avoir tout dans la classe. La solution du dessus permet de ne pas à avoir à repréciser le nombre de dimensions lorsqu'on appelle "foo".

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 371
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 371
    Points : 23 626
    Points
    23 626
    Par défaut
    Si tu comptes simplement te limiter aux points 2D et 3D, alors le plus indiqué est l'héritage : non seulement tu t'épargnes les difficultés liées aux templates mais, en plus, tu gardes l'aspect hiérarchique qui te permet de « dégrader » un point 3D en 2D. En héritant, tu peux établir une interface à respecter.

    Et puisque tu utilises le C++, autant redéfinir les opérateurs dans ta classe, pour pouvoir directement réécrire une algèbre pour tes types, plutôt qu'utiliser une fonction ordinaire détachée de tout namespace et de contexte.

    Code C++ : 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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    #include <iostream>
    using namespace std;
     
    // 2D
     
    class Point2D
    {
        public:
            double    x;
            double    y;
     
            Point2D     operator + (const Point2D &);
    };
     
    Point2D Point2D::operator + (const Point2D & p)
    {
        Point2D out = p;
        out.x += x;
        out.y += y;
     
        return out;
    }
     
    // 3D
     
    class Point3D : public Point2D
    {
        public:
            double    z;
     
            Point3D     operator + (const Point3D &);
    };
     
    Point3D Point3D::operator + (const Point3D & p)
    {
        Point3D out = p;
     
        out.Point2D::operator + (*this);
     
        out.z += z;
     
        return out;
    }
     
    int main (void)
    {
        Point2D     a,b,c;
        Point3D     d,e,f;
     
        a.x = 4; a.y = 6;
        b.x = 8; b.y = 1;
        c = a + b;
     
        cout << "(" << a.x << ";" << a.y << ")" << endl;
        cout << "(" << b.x << ";" << b.y << ")" << endl;
        cout << "(" << c.x << ";" << c.y << ")" << endl;
        cout << endl;
     
        d.x = 2; d.y = 9; d.z = 7;
        e.x = 3; e.y = 4; e.z = 5;
        f = d + e;
     
        cout << "(" << d.x << ";" << d.y << ";" << d.z << ")" << endl;
        cout << "(" << e.x << ";" << e.y << ";" << e.z << ")" << endl;
        cout << "(" << f.x << ";" << f.y << ";" << f.z << ")" << endl;
        cout << endl;
     
        return 0;
    }
    Code Shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./programme
    (4;6)
    (8;1)
    (12;7)
    
    (2;9;7)
    (3;4;5)
    (3;4;12)

    Dans l'exemple ci-dessus, on voit que Point2D additionne x et y et que, ligne 3D, Point3D commence par considérer le point 3D comme un point 2D et utilise la fonction d'addition existante, puis complète l'opération en additionnant simplement z.

    Ensuite, si tu veux être vraiment finaud, tu peux utiliser une définition de classe template récursive :

    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    #include <iostream>
    using namespace std;
     
    template <int N>
    class Point : public Point<N - 1>
    {
        public:
                    Point   ();
            void    Affiche ();
     
            int d;
    };
     
    template <>
    class Point<0>
    {
        public:
     
            int d;
    };
     
     
    template <int N>
    Point<N>::Point () :
        d(N*2)
    {
    }
     
    template <int N>
    void Point<N>::Affiche ()
    {
        if (N>1) Point<(N>1)?N-1:1>::Affiche();
        cout << d << ";";
    }
     
    int main (void)
    {
        Point<1>    a;
        Point<2>    b;
        Point<5>    c;
     
        a.Affiche(); cout << endl;
        b.Affiche(); cout << endl;
        c.Affiche(); cout << endl;
     
        b = c;
    //    c = b;
     
        return 0;
    }
    Code Shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $ ./programme
    2;
    2;4;
    2;4;6;8;10;

    Dans cet exemple, on peut définir n'importe quel point dans l'espace de dimension N. La ligne 25 est faite pour poser chaque terme des coordonnées est initialisé arbitrairement à « dimension × 2 » et on voit que c'est bien ce que l'on obtient en sortie.

    Il y a trois avantages à procéder ainsi :
    1. On n'est pas obligé de trimbaler des méta-données, ni de les initialiser. C'est utile lorsque l'on gère des milliers de points, lorsque l'on doit les sérialiser et les restaurer ;
    2. Sémantiquement, on indique au compilateur la relation qui existe entre ces différents objets ;
    3. Conséquence directe : on est capable de dire qu'un point<N> dérive directement d'un point<N-1>, quelque soit N. Et ça, ça nous débloque automatiquement toutes les facilités offertes par l'héritage.


    Ce dernier point en particulier est mis en évidence par les lignes 46 et 47 : on peut écrire Point<2> = Point<5>, dans ce cas seuls x et y sont copiés, mais pas Point<5>=Point<2> car il manque des informations.

    Ceci permet à ton compilateur de générer une erreur lui-même plutôt que confier cela au logiciel et laisser celui-ci déclencher une exception si le nombre de dimensions d'un des points est insuffisant. Et il vaut mieux car il s'agit bien d'une erreur de conception, sur la forme, mais qui pourrait ne se déclencher que beaucoup de temps après la mise en production d'un logiciel dans ce dernier cas.

    À noter enfin que l'expression tordue de la ligne 32 n'est là que pour pallier une difficulté de compilation avec ma version de G++. En principe, j'aurais dû écrire une version spéciale de la fonction affiche pour la classe Point<0> en particulier.

  7. #7
    Membre actif Avatar de Rewpparo
    Homme Profil pro
    Amateur
    Inscrit en
    Décembre 2005
    Messages
    170
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Amateur

    Informations forums :
    Inscription : Décembre 2005
    Messages : 170
    Points : 281
    Points
    281
    Par défaut
    Citation Envoyé par Trademark Voir le message
    La solution du dessus permet de ne pas à avoir à repréciser le nombre de dimensions lorsqu'on appelle "foo".
    Tu n'as pas a le préciser.
    Le compilateur choisit le paramètre du template en fonction du type que tu lui passes, inutile de le repréciser avec des chevrons. Et il vérifie à la compilation que le type est un point, puisque le type passé est Point<D>. Avec un template générique T, le compilateur peut se gourer et essayer de le faire avec un type qui n'est pas fait pour.
    Et ca marche également avec les opérateurs :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <unsigned D> class Point
    {
    public:
       Point<D> operator + (const Point<D>& p_point);
       &Point<D> operator +=(const Point<D>& p_point);
       &Point<D> operator =(const Point<D>& p_point);
    };
    Point<D> p1, p2, p3;
    p1 = p2 + p3;
    p1+=p3;
    Pour convertir d'un D a un autre, rajoute le constructeur de copie généralisé
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename D1> [explicit] Point(const Point<D1>& p_point);
    Point<2> p2;
    Point<3> p3, p;
    p = p3 + p2;
    p = p2 + p3;
    Et vu que le nombre de conversions possibles est réduite, tu peux spécialiser le template et n'implémenter que les conversions acceptables.
    Mais dans tous les cas je conseille largement explicit, changer la dimension d'un vecteur ca doit se faire de manière controlée car ca a un sens lourd.

    Citation Envoyé par Obsidian
    Ensuite, si tu veux être vraiment finaud, tu peux utiliser une définition de classe template récursive :
    Je kiffe, ce truc est tripant, j'avais jamais pensé à ca !
    Par contre aucune garantie que les coordonnées sont contigues en mémoire si ? J'en ai besoin moi pour parler vite avec opengl (qui prend un pointeur vers une array)

  8. #8
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 371
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 371
    Points : 23 626
    Points
    23 626
    Par défaut
    Citation Envoyé par Rewpparo Voir le message
    Je kiffe, ce truc est tripant, j'avais jamais pensé à ca !
    C'est-à-dire que c'est un cas particulier des templates qui sont déjà bien assez délicats. :-)

    Par contre aucune garantie que les coordonnées sont contigues en mémoire si ? J'en ai besoin moi pour parler vite avec opengl (qui prend un pointeur vers une array)
    Je voulais effectivement ajouter un paragraphe pour parler de ce point mais le commentaire ci-dessus m'a déjà pris des heures ! :-) C'est effectivement une faiblesse par rapport à un vrai tableau initialisé en une fois. Il doit cependant être possible de garantir l'agencement des données en allant regarder la norme, mais je n'ai pas eu le temps de le faire. À tout le moins, ça resterait intéressant même sur un nombre restreint de plateformes et/ou de compilateurs si ceux-ci peuvent le garantir, fût-ce moyennant quelques flags ou directives. Cela permettrait d'écrire des « accélérateurs » pour ces plateformes.

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2008
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2008
    Messages : 89
    Points : 50
    Points
    50
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Je voulais effectivement ajouter un paragraphe pour parler de ce point mais le commentaire ci-dessus m'a déjà pris des heures ! :-)
    Je remercie en général tout ceux qui répondent aux questions mais là, "double merci" ! Vos commentaires sont très instructifs ... il y a matière à cogiter pour le débutant que je suis encore ... Encore merci. Christian

  10. #10
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Si tu comptes simplement te limiter aux points 2D et 3D, alors le plus indiqué est l'héritage
    Je ne suis pas d'accord avec l'emploi d'un héritage public.
    On se traine ainsi trop de soucis à mélanger (sémantique de) valeurs et héritage.

    C'est la porte ouverte à additionner Point2D et Point3D et des hacks dans tous les sens pour essayer d'avoir un code qui supporte cela. Alors que c'est un besoin inexistant.
    Si dégradation il doit y avoir, qu'elle se fasse proprement -> par projection sur un plan selon un vecteur de projection.

    PS: les tuples c'est bien aussi pour se simplifier la vie, plus simple que les templates récursifs. Mais bon, la solution 1 avec le tableau statique de Rewpparo est de loin la meilleure.
    PPS: le code n'était pas const-correct ni symétrique (en matière de conversions implicites)
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  11. #11
    Membre actif Avatar de Rewpparo
    Homme Profil pro
    Amateur
    Inscrit en
    Décembre 2005
    Messages
    170
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Amateur

    Informations forums :
    Inscription : Décembre 2005
    Messages : 170
    Points : 281
    Points
    281
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    PPS: le code n'était pas const-correct ni symétrique (en matière de conversions implicites)
    Sur mon code ? Toujours prenneur d'améliorations !
    Const-correct mon code de prod doit l'être, ici j'ai refait à la volée de tête.
    Par contre symétrique je ne sais pas ce que ca veut dire. Si c'est le constructeur de copie généralisé, personellement je ne m'en sert pas. Que veux tu dire par là ?

  12. #12
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Je ne pensais pas à ton code.
    Mais en gros, op+ doit être libre, avec ses deux opérandes pris(es?) par référence constante.
    L'idée du côté libre est que si tu as un constructeur de conversion implicite depuis un autre type, disons T, tu pourras écrire: p2 = t + p1; et p2 = p1 + t; avec une seule fonction d'addition -- bon, OK le fait que Point<> soit template devrait neutraliser les conversions implicites ici.

    NB: on peut aller plus loin et renvoyer du const, pour que l'on ne puisse pas écrire (p2 = p1 + p3) = 42; -- chose que le langage ne permet pas avec les types (scalaires) de base.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

Discussions similaires

  1. [VB.Net] fonctions génériques pour le new
    Par ohcysp dans le forum Windows Forms
    Réponses: 3
    Dernier message: 11/09/2006, 11h47
  2. patron, templates et héritages!
    Par kleenex dans le forum C++
    Réponses: 4
    Dernier message: 05/06/2006, 22h57
  3. Fonctions génériques et listes
    Par DevloNewb' dans le forum C++
    Réponses: 6
    Dernier message: 13/01/2006, 14h47
  4. Réponses: 5
    Dernier message: 29/12/2005, 21h27
  5. [Programmation générique] template - iterator_traits
    Par Paul Atreide dans le forum Langage
    Réponses: 2
    Dernier message: 14/03/2005, 23h09

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