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 :

Transformer une classe en classe générique


Sujet :

Langage C++

  1. #1
    Membre confirmé
    Inscrit en
    Mai 2008
    Messages
    187
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 187
    Par défaut Transformer une classe en classe générique
    Bonjour,
    Pouvez vous me dire comment transformer cette classe en une classe générique?
    Je pense qu'il faut utiliser les template mais je ne vois pas du tout comment faire...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    Classe Case
    {
    public:
       Case (int res=0);
       Case * getSuivante();
       void setSuivante(Case * suivante);
       int getRes();
     
    private:
       int res;
       Case * suivante;
    }
    Merci d'avance.

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

    La première question que tu devrais te poser c'est "pour quoi faire , en ai-je vraiment besoin"...

    Le paradigme de programmation est une très bonne chose, mais il faut avouer que ce n'est pas *forcément* la solution à tous les maux, pas plus que le paradigme orienté objet n'était forcément (surtout quand on vois ce qu'en a fait java, et que C# lui a emboité le pas) la solution à tous les maux de la programmation procédurale.

    Comprenons nous bien: il ne s'agit nullement de dénigrer le paradigme procédural, loin de là...

    Mais il s'agit par contre de te faire prendre conscience que le fait d'envisager la programmation générique implique très certainement un effort de réflexion encore plus important celui que peut demander l'approche orienté objet (car on s'abstrait encore un peu plus du système).

    Dans certains cas, cet effort supplémentaire sera bénéfique et nous permettra d'économiser beaucoup plus de temps que celui que nous aurons passé à réfléchir sur la manière d'organiser les choses, mais, dans d'autres, le gain ne sera pas "suffisamment intéressant" pour justifier de se faire mal pour rien

    Mais cette remarque vaut pour l'ensemble des paradigmes plus ou moins "pointus" (il y a des fois où une simple programmation procédurale "classique" s'avère bien plus facile à mettre en œuvre qu'une programmation orientée objet )

    Maintenant, si le but est, tout simplement (parce qu'il faut aussi passer par là) de t'initier à la programmation générique, il semble clair qu'il faut bien commencer par quelque chose, et que la transformation de cette classe "simple" est une mise en bouche assez sympa

    La deuxième question à se poser consiste à repérer:
    1- ce qui peut changer (typiquement: le type de données manipulée)
    2- ce qui ne change pas (typiquement: le traitement effectué).

    Dans ton exemple, on se rend compte que, quel que soit le type de res, le comportement du constructeur et des fonctions getSuivante, setSuivante et getRes restera identique.

    La première étape "naïve" consisterait donc à... utiliser un type générique pour res.

    Cela donnerait donc, en premier jet, quelque chose de fort proche de
    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
    template <typename T>
    class Case
    {
        public:
            /* tu oubliais d'intialiser le pointeur "suivante" à NULL.... */
            Case(T res):res(res),suivante(NULL){}
            Case * getSuivante(){return suivante}
            void setSuivante(Case * s)
            {
                /* pour bien faire, il faudrait une vérification de la validité
                 * de la chose (interdisant de redéfinir la case suivante si
                 * ... elle est déjà définie, par exemple :aie:
                 */
                suivante = s;
            }
            T getRes() const {return res;}
        private:
            T res;
            Case<T> *suivante;
    };
    Mais là, nous nous rendons compte que, si cette implémentation est idéale pour les types primitifs, elle occasionne de nombreuses copies, qui peuvent devenir pénalisantes pour certains types définis par l'utilisateur.

    Il faudrait donc trouver un moyen de peaufiner un peu le travail, de manière à faire en sorte que les types primitifis (char, short, int, long, long long, float et double) soient transmis par valeur, mais que les types plus complexes (tout ce qui peut être une classe ou une structure, principalement) soient transmis par... référence.

    Il va donc falloir définir ce que l'on appelle une politique ("policy" en anglais) et... un certains nombre de traits de politique adaptés aux différentes situations.

    La politique "générale" pourrait s'appliquer à... tout ce qui est classes et structure, et prendrait donc la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    struct Policy
    {
        typedef T value_type;
        typedef T & ref_type;
        typedef T const & const_type;
    };
    Elle serait spécialisée pour chacun des types primitifs sous une forme proche de
    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
     
    /* la spécialisation pour les caractères uniques */
    template<>
    struct Policy<char>
    {
        typedef char value_type;
        typedef char & ref_type;
        typedef char const const_type;
    };
    /* la spécialisation pour les entiers de type int */
    template<>
    struct Policy<int>
    {
        typedef int value_type;
        typedef int & ref_type;
        typedef int const const_type;
    };
    /* et ainsi de suite pour les autres types primitifs */
    Si tu as lu le code de manière un peu trop rapide, tu es sans doute sur le point de t'exclamer "mais c'est chaque fois la même chose!!!", et pourtant, il y a une différence assez subtile: l'alias const_type est... une référence constante pour la "règle générale" et... un type constant pour les types primitifs, et ca, ca fera beaucoup, car il n'y a généralement aucun intérêt à... transmettre un type primitif sous la forme d'une référence (surtout si elle est constante )

    Nous allons maintenant faire en sorte d'utiliser la politique que nous venons de définir.

    Nous modifions donc notre classe en quelque chose proche de:
    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
    template <typename T>
    class Case
    {
        public:
            /* dans notre politique, nous avons principalement les alias de type
             * value_type et const_type qui nous intéressent... on s'organise
             * pour les récupérer dans notre classe :D
             */
            typedef typename Policy<T>::value_type value_type;
            typedef typename Policy<T>::const_type const_type;
            /* et nous utilisons ces types quand c'est utiles */
            Case(const_type r):res(r){}
     
            Case * getSuivante(){return suivante}
            void setSuivante(Case * s)
            {
                suivante = s;
            }
            const_type getRes() const {return res;}
        private:
            value_type res;
            Case *suivante;       
    };
    Si, pour la démonstration, on redéfini l'opérateur de flux << pour la classe Case sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<typename T>
    std::ostream & operator<<(std::ostream & ofs, Case<T> const & c)
    {
        ofs<<c.getRes();
        return ofs;
    }
    nous constatons que nous obtenons bel et bien le résultat escompté avec un code de test proche de
    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
    int main()
    {
        typedef Case<std::string> strCase;
        strCase c("salut");
        strCase c2("Monde");
        strCase c3("World");
        c.setSuivante(&c2);
        c2.setSuivante(&c3);
        strCase * ptr= &c;
        while(ptr)
        {
            std::cout<<(*ptr)<<std::endl;
            ptr=ptr->getSuivante();
        }
            typedef Case<int> intCase;
        intCase ic(12);
        intCase ic2(5412);
        intCase ic3(48930);
        ic.setSuivante(&ic2);
        ic2.setSuivante(&ic3);
        intCase * ptr2= &ic;
        while(ptr2)
        {
            std::cout<<(*ptr2)<<std::endl;
            ptr2=ptr2->getSuivante();
        }
        return 0;
    }
    Bon, ca, c'est la "version courte", car il serait encore possible d'aller beaucoup plus loin...

    Il serait, par exemple, possible de définir une politique de gestion de la mémoire, ou, comme la classe ressemble à s'y méprendre à un élément de collection (une liste simplement chainée ), il serait sans doute de bon ton de définir... cette collection, et d'y ajouter des "itérateurs", et blah, blah, blah...

    Ce qu'il faut enfin (surtout !!! ) comprendre, ces que les alias de type strCase et intCase (dans l'exemple de la fonction main que je viens de donner) n'ont strictement aucun lien entre eux (hormis une interace identique).

    Il sera donc totalement impossible (en l'état, du moins) de créer une collection d'objets qui contiendrait des intCase et des strCase en même temps

    Il est possible de faire en sorte que nous puissions créer une telle collection, mais c'est un autre débat (renseigne toi sur le "type erasure" si tu veux en savoir plus )
    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
    Membre Expert
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Par défaut
    j'aurais aussi tenance à bener le suivant pour externaliser la case et la lsite de case. une case ets une case est une case, si tu en veux une liste, fais une std::list<case<T>>.

  4. #4
    Modérateur
    Avatar de bruno_pages
    Homme Profil pro
    ingénieur informaticien à la retraite
    Inscrit en
    Juin 2005
    Messages
    3 545
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 65
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : ingénieur informaticien à la retraite
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2005
    Messages : 3 545
    Par défaut
    en fait comme la remarquer koala01il n'y a même pas besoin de la classe Case, il suffit de la remplacer par std::list (ou autre collection suivant utilisation)

    mais le 'vrai' but est d'apprendre à faire une classe générique
    Bruno Pagès, auteur de Bouml (freeware), mes tutoriels sur DVP (vieux, non à jour )

    N'oubliez pas de consulter les FAQ UML et les cours et tutoriels UML

Discussions similaires

  1. [AC-2013] Transformer une commande ciblée en générique
    Par fredjvet dans le forum Access
    Réponses: 6
    Dernier message: 07/02/2014, 15h10
  2. Transformation d'une entité en classe d'objet
    Par vg-matrix dans le forum Débuter
    Réponses: 1
    Dernier message: 10/04/2010, 22h29
  3. utilisation d'une classe de connexion générique
    Par twister9458 dans le forum Langage
    Réponses: 12
    Dernier message: 20/07/2009, 11h07
  4. Réponses: 2
    Dernier message: 06/03/2009, 09h52
  5. Réponses: 1
    Dernier message: 07/09/2005, 22h15

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