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 :

Membre générique dans une classe ("member template")


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    24
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 24
    Par défaut Membre générique dans une classe ("member template")
    Bonjour,

    J'ai déclaré une "interface" pour les conteneurs de type LIFO sous la forme d'une classe générique abstraite :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    #ifndef LIFO_H_
    #define LIFO_H_
     
    template<typename T>
    class Lifo {
    public:
        virtual void push(const T&) =0;
        virtual T pop() =0;
        virtual bool isEmpty() const =0;
        virtual int size() const =0;
    };
     
    #endif // LIFO_H_
    Plusieurs classes héritent de la classe générique abstraite Lifo<T> et en définissent les fonctions membres, dont la classe générique BasicStack<T>.

    J'ai ensuite voulu déclaré une classe de test qui pourrait manipuler n'importe quel objet à travers les fonctions membres de l'interface Lifo<T>.

    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
     
    #ifndef LIFOTEST_H_
    #define LIFOTEST_H_
     
    #include "Lifo.h"
     
    class LifoTest {
    public:
        template<typename T>
        LifoTest(Lifo<T>*);
     
        void run();
    private:
        template<typename T>
        Lifo<T>* container;
    };
     
    #include "LifoTest.tpp"
     
    #endif // LIFOTEST_H_
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    int main() {
        BasicStack<int> pile;
     
        LifoTest test(&pile);
        test.run();
     
        return 0;
    }
    Mais j'ai une erreur à la compilation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    
    Data member '_container' cannot be a member template
    
    Sachant que le membre _container est l'objet que je souhaite manipuler dans la fonction membre run(). J'ai utilisé un pointeur afin de pouvoir utiliser le polymorphisme.

  2. #2
    Membre Expert
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Par défaut
    Bonjour,

    En fait, je pense que ta classe devrait être générique dans ton cas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    template<typename T>
    class LifoTest {
    public:
        LifoTest(Lifo<T>*);
     
        void run();
    private:
        Lifo<T>* container;
    };

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

    Tu ne semble pas forcément avoir compris le principe des classes template

    L'idée du paradigme générique est de se dire que si on ne sait pas encore quel type on va manipuler, on sait par contre parfaitement comment on va le manipuler.

    On pourrait dire que cela permet de définir un concept particulier.

    En effet, à partir du moment où tu travaille avec un système LIFO, que tu manipule des téléviseurs, des morceaux de bidoches (gare à ceux qui resteront au fond du congélateur ) ou des voitures n'aura que très peu d'importance: le premier dernier entré sera le dernier sorti.

    Il n'y aura, en outre, aucun intérêt à vouloir le spécialiser plus que cela car, si tu rajoute une fonction sort (par exemple), ce n'est plus du LIFO vu que le dernier entré ne sera plus forcément... le dernier sorti

    Tu peux (pire : tu dois) donc, directement fournir le comportement correct pour les fonctions de ta classe template, et il est absolument inutile de faire croire au compilateur que les différents comportements sont susceptibles d'être modifié par une classe dérivée (vu que, en toute logique, il n'y aura pas de classe dérivée, du moins, dans l'exemple que tu donne ).

    [EDIT] Si tu veux, par exemple, faire en sorte que ta pile maintienne se différents éléments de manière différente en fonction de leur taille, il va falloir passer par ce que l'on appelle des politiques et des traits de politique, mais ca, c'est déjà une technique encore beaucoup plus évoluée
    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

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    24
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 24
    Par défaut
    koala01, j'ai l'impression que tu parle plutôt de mon choix de créer une
    interface Lifo<T> que de ma classe LifoTest qui n'est pas une classe générique, mais qui ne doit pouvoir manipuler que des types Lifo<T>.

    J'avoue pas ne pas trop saisir sur quoi porte ta remarque.
    Car, pour moi, l'idée d'utiliser une classe générique pour une pile me semble tout à fait justifié.

  5. #5
    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
    Le fait d'implémenter une pile de manière générique est totalement justifié, je ne dis pas le contraire.

    On peut même envisager de remplir la pile d'objet polymorphes, il n'y a pas de problème de ce coté là non plus.

    Mais vouloir implémenter une pile générique polymorphe n'a strictement aucun sens

    Car, si tu pars sur l'idée de déclarer des fonctions virtuelles dans ta pile, ce n'est pas pour l'utiliser avec des objets polymorphes, c'est pour rendre ta pile polymorphe (au sens objets du terme), mais, quel serait le type de pile qui pourrait hériter d'une pile générique (hormis spécialisation classique pour faire en sorte que la pile manipule des caisses)
    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

  6. #6
    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
    Je vais préciser un peu, pour que tu comprenne:

    Pour gérer ta pile, tu peux décider d'utiliser différentes politiques pour la gestion du contenu de celle-ci, allant du concept d'élément reliés les uns avec les autres au concept de "pool d'allocation", en passant par d'autres encore.

    La manière de déterminer si ta pile est vide, de déterminer le nombre d'objets qu'elle contient, d'ajouter un élément ou de le retirer dépendra, exclusivement, de la politique envisagée.

    Par exemple, si tu utilise un pool d'allocation, le nombre d'élément qu'elle contient correspond à la différence entre l'adresse du dernier élément et celle à laquelle commence le pool, alors que, si tu décide d'utiliser un concept d'éléments dont chacun est relié à son précédent, tu manipulera plutôt un compteur.

    Mais ce genre de gestion se fait... au niveau des template, par exemple sous la forme de:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* je considère que PoolAllocator dispose d'une fonction empty ;)
     */
    template<typename T, typename Allocator=PoolAllocator>
    class Lifo
    {
        public:
            bool empty() const{return allocator_.empty();}
        private:
            Allocator allocator_;
    };
    A partir de là:

    Si tu viens à envisager une autre politique de gestion des éléments, il te "suffit" de fournir un nouveau type d'allocateur, et de préciser (comme deuxième type) que c'est celui qu'il faut utiliser.

    Par contre, que tu veuille manipuler des caisses, des boites de concerves, de Truc ou des Bidules ne changera absolument pas la manière de fonctionner de ta pile

    Au niveau de LifoTest, tu sais quel type d'objet ta pile va devoir gérer, et tu as, sans doute, une idée relativement précise de la manière qui sera la plus adéquate de gérer ces éléments (de la politique que tu vas utiliser pour le faire).

    Il te suffira donc, tout simplement, de créer un objet de type Lifo en le spécialisant selon tes besoins:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    class LifoTest
    {
        public:
            Message & top() const{return lifo_.top();}
            bool isEmpty() cosnt{return lifo_.empty();}
            void push(Message const& m){lifo_.push();}
            /* ...*/
        private:
            Lifo<Message, SeparateElement> lifo_;
    };
    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

  7. #7
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    24
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 24
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Mais vouloir implémenter une pile générique polymorphe n'a strictement aucun sens
    Je suis d'accord avec toi. Je pense que j'aurai dû un peu plus préciser pourquoi j'ai choisi cela.
    Comme dans le sujet précédent, ce que je fait n'a pour seul intérêt que l'apprentissage des concepts du C++. J'essaye donc de trouver des manières d'utiliser tout ces concepts.
    Dans ce cas, la création d'une "interface" pour ma pile n'est là que parce que je voulais tester la création d'objets polymorphes. Je voulais en même temps pouvoir définir trois implémentation de mon interface Lifo<T> :
    • la première, qui correspond à celle que j'ai exposé dans le sujet précédent, effectue une réallocation à chaque ajout/suppression d'un élément (je sais, c'est horrible )
    • la seconde, que je n'ai pas encore écrite, est décrite dans ce post
    • et peut-être une troisième qui utiliserait en interne une liste chaînée (que je m'amuserais bien sûr à coder moi même)


    Pour ce qui est de ma classe LifoTest, je suis parti avec une idée de la manière dont je pourrais l'instancier et lancer le test, mais également de la manière de l'implémenter .

    Voici comment je pensais écrire la fonction membre run() :
    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
     
    #include <iostream>
     
    using namespace std;
     
    template<typename L : Lifo<typename T> >
    LifoTest<L>::run() {
        /*
         * _container serait déclaré en tant que membre comme suit :
         * L _container;
         */
        if (!_container.isEmpty()) { 
            cerr << "ERROR : new LIFO container will be empty." << endl;
        }
     
        T element;
     
        cout << element << "Push to LIFO container." << endl;
        _container.push(element);
        cout << "Container size : " << _container.size();
     
        if (_container.isEmpty()) {
            cerr << "ERROR : LIFO container will not be empty after element was pushed." << endl;
        }
     
        cout << "Pop element from LIFO container." << endl;
        T popedElement = _container.pop();
        if(element != popedElement) {
            cerr << "ERROR: Poped element is different from which was pushed.";
        }
     
        if (!_container.isEmpty()) {
            cerr << "ERROR : LIFO container will be empty after last element was poped." << endl;
        }
    }
    Et comment je pensais utiliser la classe LifoTest :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    int main () {
        LifoTest<BasicStack<int> > test1;
        LifoTest<ImprovedMemoryStack<float> > test2;
        LifoTest<ChainedStack<string> > test3;
     
        test1.run();
        test2.run();
        test3.run();
     
        return 0;
    }
    Ce code (qui va surement vous paraître horrible ) vient du fait qu'en Java , il est possible lors de la déclaration d'un template, de forcer le type générique (ici L) à être une classe dérivée d'une certaine classe (ici Lifo<T>).

    Laissez-moi vous expliquer la ligne de déclaration du template (qui ne fonctionne pas bien-sûr) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    template<typename L : Lifo<typename T> >
    La classe générique LifoTest accepte un paramètre template L qui doit être une classe dérivée de Lifo. Mais je ne veux pas contraindre à utiliser Lifo<int> ou Lifo<string>, donc je rajoute un second paramètre de template inclu dans le premier ("template template parameter").

  8. #8
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Dans ta classe LifoTest, comment veux tu que le compilateur trouve tout seul le bon type à donner à container au moment de l'instantiation du template ? Il ne peut pas ! La solution à ceci, cf Aleph69.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  9. #9
    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
    Citation Envoyé par Davidbrcz Voir le message
    Dans ta classe LifoTest, comment veux tu que le compilateur trouve tout seul le bon type à donner à container au moment de l'instantiation du template ? Il ne peut pas ! La solution à ceci, cf Aleph69.
    Et encore...

    Pourquoi utiliser un pointeur si tu sais pertinemment quel sera le type réel de T
    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

Discussions similaires

  1. Instantiation générique dans une classe paramétrée
    Par Dark_TeToN dans le forum Débuter avec Java
    Réponses: 7
    Dernier message: 07/12/2013, 09h15
  2. Réponses: 4
    Dernier message: 20/06/2013, 18h07
  3. Réponses: 2
    Dernier message: 18/04/2012, 17h47
  4. Réponses: 2
    Dernier message: 07/12/2009, 16h50
  5. membre statique dans une classe
    Par motrin dans le forum VB 6 et antérieur
    Réponses: 4
    Dernier message: 30/12/2005, 15h15

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