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 :

Pointeur de fonction membre vers une méthode de classe dérivée


Sujet :

C++

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    162
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Mai 2002
    Messages : 162
    Points : 88
    Points
    88
    Par défaut Pointeur de fonction membre vers une méthode de classe dérivée
    Bonjour,

    Je me pose des questions sur la qualité d'un code où une classe de base utilise un pointeur vers une fonction membre, pointant vers une méthode d'une classe dérivée.

    Je vous décris un peu le programme. J'ai un ensemble d'objets dérivant d'une classe de base. Le programme doit faire évoluer ces objets en appelant une méthode progress() sur chacun d'eux. La façon d'évoluer de chaque objet n'est pas constante. Par exemple un objet peux commencer par se déplacer, puis change de comportement au bout d'une minute pour changer de taille.

    Les différentes évolutions d'objet sont inconnues de la classe de base. Une solution serait de déclarer la méthode progress() comme virtuelle, et chaque classe fille ferait un switch pour appeler la bonne méthode d'évolution.

    Un autre méthode serait d'utiliser un pointeur sur une fonction membre. J'ai écrit le code suivant pour tester, j'aimerais savoir ce que vous en pensez.

    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
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    //------------------------------------------------------------------------------
    class base
    {
    public:
      typedef void (base::*progress_method_type)();
     
      base();
      void progress();
     
    protected:
      void hook(progress_method_type f) { m_progress_method = f; }
     
    private:
      void idle() {}
     
    private:
      progress_method_type m_progress_method;
     
    }; // class base
     
    base::base()
      : m_progress_method(&base::idle)
    {
     
    }
     
    void base::progress()
    {
      // on appelle la méthode définie par la classe de base
      (this->*m_progress_method)();
    }
     
    //------------------------------------------------------------------------------
    class derived
      : public base
    {
    public:
      derived();
     
    private:
      void first_part();
      void second_part();
     
    private:
      int m_count;
     
    }; // class derived
     
    derived::derived()
      : m_count(0)
    {
      // on met le progress sur une méthode de la classe dérivée
      hook((progress_method_type)&derived::first_part);
    }
     
    void derived::first_part()
    {
      ++m_count;
     
     if (m_count == 24)
        hook((progress_method_type)&derived::second_part);
    }
     
    void derived::second_part()
    {
      --m_count;
     
     if (m_count == 0)
        hook((progress_method_type)&derived::first_part);
    }
     
    //------------------------------------------------------------------------------
    int main()
    {
      derived d;
     
      while (true)
        d.progress();
     
      return 0;
    }
    Compilé avec g++ 4.2.3, ce code fonctionne très bien. Cependant, je ne sait pas si c'est exceptionnel ou parfaitement défini par la norme. Quand une méthode de "derived" est appelée par "base", est-ce que le pointeur "this" pointera toujours vers la bonne instance de "derived" ?

  2. #2
    Membre éprouvé
    Avatar de Spout
    Profil pro
    Ingénieur systèmes et réseaux
    Inscrit en
    Février 2007
    Messages
    904
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur systèmes et réseaux

    Informations forums :
    Inscription : Février 2007
    Messages : 904
    Points : 1 067
    Points
    1 067
    Par défaut
    Citation Envoyé par YéTeeh Voir le message
    Quand une méthode de "derived" est appelée par "base", est-ce que le pointeur "this" pointera toujours vers la bonne instance de "derived" ?
    J'ai du mal à comprendre, car une classe mère ne connaît pas ses éventuelles classe filles. Donc comment appeler, à partir de la classe mère, une méthode d'une classe fille qu'elle ne connait pas?

    Plus globalement, et tu l'as cité dans ton post, la meilleure solution pour toi serait, je pense, d'utiliser une fonction virtuelle dans la classe mère, voire même la rendre abstraite si elle n'est jamais directement instanciée.
    La solution que tu proposes ici me paraît brouillon et m'inspire plus de la bidouille que du respect de la norme.

    A prendre avec des pincettes, car je ne suis pas (encore ) super doué sur ce sujet.
    "L'ordinateur obéit à vos ordres, pas à vos intentions." [Anonyme]

  3. #3
    screetch
    Invité(e)
    Par défaut
    retire les C-cast venu de l'enfer et insère des static_cast a la place. Si ca ne marche pas, c'est que ca ne marche pas.

  4. #4
    Membre actif
    Profil pro
    Inscrit en
    Août 2007
    Messages
    190
    Détails du profil
    Informations personnelles :
    Localisation : France, Maine et Loire (Pays de la Loire)

    Informations forums :
    Inscription : Août 2007
    Messages : 190
    Points : 219
    Points
    219
    Par défaut
    Salut,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    hook((progress_method_type)&derived::first_part);
    Le fait que tu sois obliger de caster pour que ton code compile prouve bien qu'il y a un problème. Tu pourrais par exemple utiliser le CRTP comme ceci :
    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
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    template<typename T>
    class base
    {
    public:
      typedef void (T::*progress_method_type)();
     
      base();
      void progress();
     
    protected:
      void hook(progress_method_type f) { m_progress_method = f; }
     
    private:
      void idle() {}
     
    private:
      progress_method_type m_progress_method;
     
    }; // class base
     
    template<typename T>
    base<T>::base()
      : m_progress_method(&base<T>::idle)
    {
     
    }
     
    template<typename T>
    void base<T>::progress()
    {
      // on appelle la méthode définie par la classe de base
      (static_cast<T*>(this)->*m_progress_method)();
    }
     
    //------------------------------------------------------------------------------
    class derived
      : public base<derived>
    {
    public:
      derived();
     
    private:
      void first_part();
      void second_part();
     
    private:
      int m_count;
     
    }; // class derived
     
    derived::derived()
      : m_count(0)
    {
      // on met le progress sur une méthode de la classe dérivée
      hook(&derived::first_part);
    }
     
    void derived::first_part()
    {
      ++m_count;
     
      if (m_count == 24)
        hook(&derived::second_part);
    }
     
    void derived::second_part()
    {
      --m_count;
     
      if (m_count == 0)
        hook(&derived::first_part);
    }
     
    //------------------------------------------------------------------------------
    int main()
    {
      derived d;
     
      while (true)
        d.progress();
     
      return 0;
    }

  5. #5
    Membre confirmé

    Inscrit en
    Août 2007
    Messages
    300
    Détails du profil
    Informations forums :
    Inscription : Août 2007
    Messages : 300
    Points : 527
    Points
    527
    Par défaut
    Si on veut garder le principe de "hook" (ce qui est parfois très utile), on peut très bien le faire dans la classe dérivée avec du C++ propre sans cast ni écriture brouillonne.
    base::progress devient alors virtuelle pure, et l'implémentation actuelle de progress (avec la donnée membre) simplement déplacée dans derived. L'intérêt est que cela peut être fait à chaque niveau de dérivation, localement et proprement.
    De plus, toute l'infrastructure du C++ pour les arborescences complexes (diamants) reste utilisable sans complexité supplémentaire.
    "Maybe C++0x will inspire people to write tutorials emphasizing simple use, rather than just papers showing off cleverness." - Bjarne Stroustrup
    "Modern C++11 is not your daddy’s C++" - Herb Sutter

  6. #6
    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 : 32
    Localisation : Suisse

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

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Points : 4 732
    Points
    4 732
    Par défaut
    ac_wingless>> le seul problème, c'est que les fonctions à appeler dans progress ne sont pas toujours les même.

    Pour combler ce problème, j'imagine bien un std::vector de boost::function dans la classe de base, que chaque classe dérivée remplirai à sa gise, puis une boost::function qui "pointerai" sur la fonction à appeler actuellement.
    Ca donne un truc du genre (non testé):
    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
     
    class base
    {
    boost::function<void ()> m_actual;
    std::vector<boost::function<void ()> >m_listFct;
    public:
     virtualvoid progress() =0;
    };
     
    class d: public base
    {
    protected:
     
    int i;
     
    void progress1(){/*what you want*/}
    void progress2(){/*what you want*/}
     
    public:
    d():i(0)
    {
    m_listFct.push_back(boost::bind(&d::progress1,*this,_1));
    m_listFct.push_back(boost::bind(&d::progress2,*this,_1));
     
    m_actual.swap(m_listFct[0]);
    }
     
    void progress(){m_actual();if(i++==24)m_actual.swap(m_listFct[1]);}
    };
    Le seul inconvéniant est que les fonctions de à appeler doivent toutes avoir le même prototype.
    "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)

  7. #7
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    162
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Mai 2002
    Messages : 162
    Points : 88
    Points
    88
    Par défaut
    Citation Envoyé par Davidbrcz Voir le message
    ac_wingless>> le seul problème, c'est que les fonctions à appeler dans progress ne sont pas toujours les même.
    Tout à fait, c'est là qu'est la difficulté en fait. Grâce à vos conseils et à un collègue, j'ai une solution qui me semble propre. Au lieu d'avoir un pointeur sur une méthode membre, j'utilise un objet intermédiaire qui va se charger d'appeler la méthode adéquate. Voici le code :

    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
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    //------------------------------------------------------------------------------
    class progress_function
    {
    public:
      virtual void execute(double t) = 0;
     
    }; // class progress_function
     
    //------------------------------------------------------------------------------
    class base
    {
    public:
      base();
      void progress(double t);
     
    protected:
      void hook(progress_function* f) { m_progress_function = f; }
     
    private:
      progress_function* m_progress_function;
     
    }; // class base
     
    base::base()
      : m_progress_function(NULL)
    {
     
    }
     
    void base::progress(double t)
    {
      if (m_progress_function != NULL)
        m_progress_function->execute(t);
    }
     
    //------------------------------------------------------------------------------
    template<typename ClassType>
    class simple_progress_function
      : public progress_function
    {
    public:
      typedef void (ClassType::*progress_method_type)(double t);
     
    public:
      simple_progress_function( ClassType& self, progress_method_type m )
        : m_self(self), m_progress_method(m)
      { }
     
      void execute(double t)
      {
        (m_self.*m_progress_method)(t);
      }
     
    private:
      ClassType& m_self;
     
      progress_method_type m_progress_method;
     
    }; // class simple_progress_function
     
    //------------------------------------------------------------------------------
    class derived
      : public base
    {
    public:
      derived();
     
    private:
      void first_part(double t);
      void second_part(double t);
     
    private:
      int m_count;
     
      simple_progress_function<derived> m_first_progress;
     
      simple_progress_function<derived> m_second_progress;
     
    }; // class derived
     
    derived::derived()
      : m_count(0), m_first_progress(*this, &derived::first_part),
        m_second_progress(*this, &derived::second_part)
    {
      // on met le progress sur une méthode de la classe dérivée
      hook(&m_first_progress);
    }
     
    void derived::first_part(double t)
    {
      ++m_count;
     
      if (m_count == 24)
        hook(&m_second_progress);
    }
     
    void derived::second_part(double t)
    {
      --m_count;
     
      if (m_count == 0)
        hook(&m_first_progress);
    }
     
    //------------------------------------------------------------------------------
    int main()
    {
      derived d;
     
      while (true)
        d.progress(1);
     
      return 0;
    }
    Remarquez que la classe template est totalement facultative, elle n'est là que pour simplifier l'appel à un progress simple. Elle utilise un pointeur sur une fonction membre qui correspond tout à fait à l'objet concerné.

    Ce qui me chagrine un peu c'est que la classe "base" n'a aucune garantie que le traitement effectué par progress_function::execute() porte bien sur l'instance de la classe dérivée. Mais bon, on ne peut pas tout avoir.

    Merci pour vos commentaires.

  8. #8
    Membre confirmé

    Inscrit en
    Août 2007
    Messages
    300
    Détails du profil
    Informations forums :
    Inscription : Août 2007
    Messages : 300
    Points : 527
    Points
    527
    Par défaut
    Citation Envoyé par Davidbrcz Voir le message
    ac_wingless>> le seul problème, c'est que les fonctions à appeler dans progress ne sont pas toujours les même.
    Pas compris.

    Je vais développer un peu (quoique c'est pratiquement le code initial à une ou deux inversions de ligne):
    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
     
    struct base { virtual void progress() {} };
     
    //------------------------------------------------------------------------------
    class derived : public base
    {
    public:
      typedef void (derived::*progress_method_type)();
      derived() : m_count(0), m_progress_method (&derived::first_part) {}
        virtual void progress()
        {
          (this->*m_progress_method)();
        }
     
    private:
      void first_part();
      void second_part();
     
    protected:
      void hook(progress_method_type f) { m_progress_method = f; } 
     
    private:
      progress_method_type m_progress_method;
      int m_count;
     
    }; // class derived
     
    void derived::first_part()
    {
      ++m_count;
     
     if (m_count == 24)
        hook(&derived::second_part);
    }
     
    void derived::second_part()
    {
      --m_count;
     
     if (m_count == 0)
        hook(&derived::first_part);
    }
    Voilà, le principe du hook est identique, autorise n'importe quelle fonction (pas toujours les mêmes bien sûr), il peut être répété à chaque niveau de dérivation sans interférence, est indifférent à la structure de hiérarchie (ça marche en diamant, il suffit de décider si on veut utiliser les classes plus hautes), assure que la fonction est bien appliquée au bon objet, et ne réclame aucun cast.
    "Maybe C++0x will inspire people to write tutorials emphasizing simple use, rather than just papers showing off cleverness." - Bjarne Stroustrup
    "Modern C++11 is not your daddy’s C++" - Herb Sutter

  9. #9
    Membre actif
    Profil pro
    Inscrit en
    Août 2007
    Messages
    190
    Détails du profil
    Informations personnelles :
    Localisation : France, Maine et Loire (Pays de la Loire)

    Informations forums :
    Inscription : Août 2007
    Messages : 190
    Points : 219
    Points
    219
    Par défaut
    Salut YéTeeh,

    La solution que j'ai proposé quelques messages plus haut me semble plus simple non ?

  10. #10
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    162
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Mai 2002
    Messages : 162
    Points : 88
    Points
    88
    Par défaut
    Citation Envoyé par Montag Voir le message
    La solution que j'ai proposé quelques messages plus haut me semble plus simple non ?
    En fait je ne peux pas avoir de paramètre template à "base" parce que la boucle des appels aux progress() se fait sur une liste de "base*".

    Citation Envoyé par ac_wingless Voir le message
    Je vais développer un peu
    Ta méthode me semble très bien. Tous les objets dérivant de "base" ne sont pas obligés, du coup, d'utiliser d'objet fonction. Pour les objets n'ayant qu'un seul type de progress à faire il n'y a qu'à réimplémenter base::progress(), ce qui est quand même très simple.

    Je crois que je vais m'orienter vers cette solution.

  11. #11
    Membre actif
    Profil pro
    Inscrit en
    Août 2007
    Messages
    190
    Détails du profil
    Informations personnelles :
    Localisation : France, Maine et Loire (Pays de la Loire)

    Informations forums :
    Inscription : Août 2007
    Messages : 190
    Points : 219
    Points
    219
    Par défaut
    Citation Envoyé par YéTeeh
    En fait je ne peux pas avoir de paramètre template à "base" parce que la boucle des appels aux progress() se fait sur une liste de "base*".
    Oui effectivement c'est n'importe quoi ce que j'ai proposé. Désolé.

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

Discussions similaires

  1. Réponses: 2
    Dernier message: 24/03/2014, 08h10
  2. Réponses: 6
    Dernier message: 08/04/2011, 18h30
  3. Réponses: 1
    Dernier message: 09/04/2010, 11h15
  4. Réponses: 11
    Dernier message: 11/04/2007, 18h33
  5. Pointeur sur des fonctions membres d'une classe
    Par Muetdhiver dans le forum C++
    Réponses: 3
    Dernier message: 15/02/2006, 11h35

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