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 :

Composant générique et méthodes virtuelles


Sujet :

C++

  1. #1
    Futur Membre du Club
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    Points : 5
    Points
    5
    Par défaut Composant générique et méthodes virtuelles
    Bonjour à tous,

    Je suis en train d'essayer de faire un composant générique avec lequel j'ai un petit soucis.

    Voici le problème. Tout d'abord, il y a une série de petites classe de base destinée à être dérivées:

    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
    namespace generic
    {
      struct base1 {
        base1(){}
        ~base1(){}
        virtual void func(void){cout << "generic::base1" << endl;}
      };
     
      struct base2 {
        base2(){}
        ~base2(){}
        virtual void func(void){cout << "generic::base2" << endl;}
      };
     
      struct base3 {
        base3(){}
        ~base3(){}
        virtual void func(void){cout << "generic::base3" << endl;}
      };
     
      struct deriv: virtual public base1,
                    virtual public base2,
                    virtual public base3 {
        deriv(){}
        ~deriv(){}
        virtual void func(void){cout << "generic::deriv" << endl;}
      };
    }
    Puis ensuite vient l'implémentation spécifique :

    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
    namespace custom
    {
      struct base1: virtual public generic::base1 {
        base1(){}
        ~base1(){}
        virtual void func(void){cout << "custom::base1" << endl;}
      };
     
      struct base2: virtual public generic::base2 {
        base2(){}
        ~base2(){}
        virtual void func(void){cout << "custom::base2" << endl;}
      };
     
      struct base3: virtual public generic::base3 {
        base3(){}
        ~base3(){}
        virtual void func(void){cout << "custom::base3" << endl;}
      };
     
      struct deriv: virtual public generic::deriv,
                    virtual public custom::base1,
                    virtual public custom::base2,
                    virtual public custom::base3 {
        deriv(){}
        ~deriv(){}
        virtual void func(void){cout << "custom::deriv" << endl;}
      };
    }
    Lors de l'utilisation ces classes, je ne parvient pas à appeler les fonctions membres souhaitées. Ainsi le programme suivant:

    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(int, char *[])
    {
      int size=sizeof(custom::deriv);
     
      generic::deriv *ptr=new custom::deriv;
     
      ptr->base1::func();
      ptr->base2::func();
      ptr->base3::func();
      ptr->deriv::func();
     
      return 0;
    }
    génère la sortie:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    generic::base1
    generic::base2
    generic::base3
    generic::deriv
    alors que celle attendue devrait plutôt ressembler à:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    custom::base1
    custom::base2
    custom::base3
    custom::deriv
    J'aurais donc voulu savoir ce qui clochait dans ma conception ?

    Merci d'avance.

  2. #2
    Membre confirmé
    Avatar de haraelendil
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2004
    Messages
    283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2004
    Messages : 283
    Points : 533
    Points
    533
    Par défaut
    déjà c'est normal que tu parles de classes mais que tu utilise le mot-clé struct?

    Et pourquoi les virtual dans les déclarations d'héritage de tes classes dérivées?

  3. #3
    Futur Membre du Club
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    Points : 5
    Points
    5
    Par défaut
    class ou struct n'a aucune importance : en utilisant struct, je fait en sorte que la protée des membres soit publique par défault alors qu'avec la directive class les membres sont privés.
    Ensuite le fait d'ajouter virtual lors de la dérivation m'assure qu'aucun doublon d'une classe de base ne sera présent dans la classe dérivée. Ici dans cet exemple, cela a effectivement peu d'intérêt ! En revanche dès que qu'il y aura des données membres dans les classes de bases, l'unicité de cette données n'est plus valable comme par exemple heritage en losange:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
        A
       / \
       B C
       \ /
        D

  4. #4
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par haraelendil Voir le message
    déjà c'est normal que tu parles de classes mais que tu utilise le mot-clé struct?
    http://cpp.developpez.com/faq/cpp/in...e_class_struct

    Et pourquoi les virtual dans les déclarations d'héritage de tes classes dérivées?
    http://cpp.developpez.com/faq/cpp/in...RITAGE_virtuel

  5. #5
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par ludovic.barrillie Voir le message
    J'aurais donc voulu savoir ce qui clochait dans ma conception ?
    Possible. Quel est le but de ta modélisation ?
    Si l'héritage en diamant peut être éviter, perso je trouve que c'est pas plus mal !

  6. #6
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    C'est un sacré mix entre le namespace generic et le namespace custom. Note au passage que ton titre de discussion est trompeur. Générique est employé pour parler de template et là, il n'y en a pas...

    Sinon, ton problème est le suivant : ptr->base1::func Cette instruction dit d'appeler la fonction func définie dans la classe base1 mais sans utiliser le mécanisme virtuel (sinon, il fallait faire ptr->func() ), donc en s'appuyant sur le type statique de la variable ptr. Or ptr est une variable de type statique generic::deriv. Le seul base1 lié à ce type est generic::base1. C'est pour cela que c'est cette fonction qui est appelée.
    Voir ce tuto : les fonctions virtuelles en C++.

    La question est : qu'essaies-tu de faire ? Car là, j'ai l'impression d'une approche assez tordue

  7. #7
    Futur Membre du Club
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    Points : 5
    Points
    5
    Par défaut
    Voici en fait le détail du besoin.
    Toud d'abord nous avons des classes d'interfaces : generic::deriv et generic::baseX. Chaque base représente une fonctionnalité d'un composant principal qui les englobe (deriv).
    Ensuite viennent les classes d'implémentation custom::baseX et custom::deriv qui seront bien entendu masquées aux utilisateurs.

    Pour être plus concret, voici un exemple d'utilisation. Les classes d'interface pourrait représenter un prériphérique E/S.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    namespace generic {
      struct read {
        virtual void exec(void);
      };  
      struct write {
        virtual void exec(void);
      };  
      struct device: virtual public read,
                     virtual public write {
        virtual void exec(void);
      };  
    }
    Ensuite la "spécialisation" du prériphérique (par ex. un fichier) :

    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
    namespace file {
      struct descriptor {}
      };  
      struct read: virtual public descriptor,
                   virtual public generic::read {
        virtual void exec(void) {fscanf(...);}
      };  
      struct write: virtual public descriptor,
                    virtual public generic::write {
        virtual void exec(void) {fprintf(...);}
      };  
      struct device: virtual public descriptor,
                     virtual public read,
                     virtual public write,
                     virtual public generic::device {
        virtual void exec(void) {read::exec(); write::exec();}
      };  
    }
    Le but de cet implémentation est d'offrir différent niveau de fonctionnalités en fonction du contexte d'utilisation (complet: device, en lecture seule: read ou en écriture write):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void write(generic::write *dev)
    {
      dev->exec("World!\n");
    }
     
    int main(int argc, char *argv[])
    {
      generic::device *dev=new file::device(argv[1]);
     
      dev->write::exec("Hello ");
      write(dev);
    }
    La deuxième contrainte est que je suis uniquement auteur de ce composant (pas utilisateur). Les utilisateurs ne sont pas très initiés au C++. Je souhaite donc pouvoir au final avoir une interface relativement simple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
      generic::device *dev=new file::device(path);
      dev->write::exec("my data");
    dev->read::exec(data);[/CODE]

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    A vrai dire, je me demande pourquoi tu souhaite faire que tes classes custom dérivent à la fois de generic::derivee et de custom::baseX...

    Après tout, si tu fait dériver tes différentes classe de base dans l'espace de noms custom des bases équivalentes dans l'espace de noms generic et que tu fait dériver ta classe dérivée de ces seules classes de base (custom::baseX), tu obtient tout à fait l'interface qui t'intéresse, et rien ne t'empêche, l'héritage aidant, de considérer un pointeur ou une référence sur ta classe custom::derivee comme... un pointeur ou une référence sur generic::baseX...

    Avec, en plus, l'avantage d'éviter les différents problèmes induits par l'héritage virtuel et de pouvoir créer d'autres classes dérivées ayant des interfaces plus ou moins restreintes / complètes.

    Comme les noms baseXXX sont relativement peu parlant, imaginons les interfaces de base (génériques)
    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
    namespace generic
    {
        class unserializable
        {
            public:
                virtual void fromFile(std::string const & filename) = 0;
        };
        class serializable
        {
            public:
                virtual void toFile(std::string const & filename) const =0;
        };
        class printable
        {
            public:
                virtual void print() const = 0;
        };
    }
    tu peux te dire qu'il est possible de regrouper les trois interface en une seule, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    namespace generic
    {
        class MyInterface : public unserializable,
                            public serializable,
                            public printable
        {
            public:
                /* aucune redéfinition des fonctions virtuelles pures héritées,
                 * mais ajout éventuel de fonctions propres à MyInterface
                 */
        };
    }
    Lorsque tu veux une implémentation spécifique, tu as deux possibilités:

    Soit, (cela semble être la meilleure solution ) tu fait directement dériver ta classe spécifique de MyInterface et tu redéfini les fonctions nécessaires:
    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
    namespace custom
    {
        class MyClass : public ::generic::MyInterface
        {
            public:
                /* on n'a pas le choix, il faut au minimum redéfinir les fonctions
                 */
                virtual void fromFile(std::string const & filename);
                virtual void toFile(std::string const & filename) const;
                virtual void print() const;
                /* et les fonction virtuelles pures éventuellement ajoutées
                 * dans MyInterface
                 */
        };
    }
    soit tu fais dériver certaines classes de base des parties d'interface déjà défini, et ta classe concrète de ces classes dérivées:
    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
    namespace custom
    {
        class MyClass;
        class serializableImpl : public ::generic::serializable
        {
            public:
                virtual void toFile(std::string const & filename) const
                {
                    std::ofstream ofs(filename.c_str));
                    /* downcast "dangereux", mais envisageable */
                    MyClass const & ref=static_cast<MyClass const &>(*this);
                    ofs<<ref.value();
                }
        };
        class unserializableImpl : public ::generic::unserializable
        {
            virtual void fromFile(std::string const & filename)
            {
                    std::ofstream ofs(filename.c_str));
                    Type val;
                    ifs>>val;
                    /* downcast "dangereux", mais envisageable */
                    MyClass const & ref=static_cast<MyClass &>(*this);
                    ref.setValue(val);
            }
        };
        class printableImpl :public ::generic::printable
        {
            public:
                virtual void print() const
                {
                    /* downcast "dangereux", mais envisageable */
                    MyClass const & ref=static_cast<MyClass const &>(*this);
                    std::cout<<ref.value();
                }
        };
        class MyClass : public serializableImpl,
                        public unserializableImpl,
                        public printableImpl
                        /* !!! aucun besoin d'hériter de MyInterface, tout est
                         * fourni par le reste
                         */
        {
            public:
                /* éventuellement quelques fonctions propre à MyClass, comme */
                Type const & value() const{return value_;}
                 void setValue(Type const & v){value_=v;}
            private:
                Type value_;
        };
    }
    Tu remarquera qu'il n'y a plus le moindre héritage en diamant, quel que soit la solution envisagé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

  9. #9
    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
    Et pourquoi ne pas utiliser un bon coup de méta-programmation là ?
    La structure est plantée, il n'y a plus qu'a transformer.
    "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)

  10. #10
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par Davidbrcz Voir le message
    Et pourquoi ne pas utiliser un bon coup de méta-programmation là ?
    La structure est plantée, il n'y a plus qu'a transformer.
    Ca me démangeait de le dire depuis le début, mais j'ai pas osé. Pas le temps d'écrire un trop gros bout de code !

  11. #11
    Futur Membre du Club
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    Points : 5
    Points
    5
    Par défaut
    Comme déjà dit précédemment, une de mes contraintes et que le code proposé devra offrir une interface simple car les utilisateurs ne sont pas des experts du C++.
    Pour ma part j'ai essayé et ne suis pas parvenu à le faire (de façon simple j'entends) mais je suis pas contre une bonne idée

  12. #12
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    En tout cas, ça sera beaucoup moins compliqué que des héritages en diamant !!!
    Un bon design, un exemple clair, et c'est parti !

  13. #13
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    +1 avec mes camarades qui proposent une approche générique avec plus ou moins l'idée d'une politique.

    Pour une approche plus classique, et puisque ton besoin est vraiment de séparer l'interface proposée des détails d'implémentations, pourquoi ne pas opter plutôt pour un pimpl-idiom. A la volée à partir de ton 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
    115
    116
    117
    118
    #include <iostream>
     
    struct noncopiable
    {
    protected:
        noncopiable(){}
    private:
        noncopiable(noncopiable const&);
        noncopiable&operator=(noncopiable);
    };// ou a reprendre de boost ou de C++0x.
     
    namespace file {
        struct descriptor
        {
            virtual ~descriptor(){}
        };
        struct read_impl: virtual public descriptor
        {
            virtual read_impl*clone()const{return new read_impl();}
            void exec()const
            {
                std::cout<<"file::read_impl\n";
            }
            virtual ~read_impl(){}
     
        };
        struct write_impl: virtual public descriptor
        {
            virtual write_impl*clone()const{return new write_impl();}
            void exec(void) const
            {
                std::cout<<"file::write_impl\n";
            }
            virtual ~write_impl(){}
        };
        struct device_impl:public read_impl,public write_impl
        {
            virtual device_impl*clone()const{return new device_impl();}
            void exec(void) const
            {
                read_impl::exec(); write_impl::exec();
            }
            virtual ~device_impl(){}
        };
    }
     
    namespace generic {
        struct read :private noncopiable{
            read(::file::read_impl const&impl_):impl(impl_.clone()){}
            void exec(void)const
            {
                impl->exec();
            }
        protected:
            ~read(){delete impl;}
        private:
            ::file::read_impl *const impl;
        };
     
        struct write :private noncopiable{
            write(::file::write_impl const&impl_):impl(impl_.clone()){}
            void exec(void) const
            {
                impl->exec();
            }
        protected:
            ~write(){delete impl;}
        private:
            ::file::write_impl *const impl;
        };
     
        struct device:public read, public write{
            device(::file::device_impl const&impl_)
                :read(impl_),write(impl_),
                impl(impl_.clone())
            {}
     
            void exec(void)const
            {
                impl->exec();
            }
            virtual ~device(){delete impl;}
        private:
            ::file::device_impl *const impl;
        };
    }
     
    void interface_write(generic::write const &dev)
    {
        dev.exec();
    }
     
    void interface_read(generic::read const &dev)
    {
        dev.exec();
    }
     
    void interface_device(generic::device const &dev)
    {
        dev.exec();
    }
    int main()
    {
        file::device_impl impl;
        generic::device dev(impl);
     
        std::cout<<"explicit write : \n";
        dev.write::exec();
        std::cout<<"using write interface: \n";
        interface_write(dev);
        std::cout<<"using read interface : \n";
        interface_read(dev);
     
        std::cout<<"using device interface : \n";
        interface_device(dev);
     
        return 0;
    }
    1/je ne connais pas descriptor, mais es-tu sûr d'avoir besoin d'un héritage ? Une composition ne suffit pas ?
    2/ Les xxx_impl peuvent être juste des classes abstraites utilisant le pattern NVI si tu as besoin d'un point de variation à ce niveau.

Discussions similaires

  1. Appel d'une méthode virtuelles
    Par BIPBIP59 dans le forum C++Builder
    Réponses: 4
    Dernier message: 24/03/2006, 14h00
  2. Méthodes virtuelle et implémentation
    Par slate dans le forum C++
    Réponses: 2
    Dernier message: 16/02/2006, 17h16
  3. méthodes virtuelles
    Par ep31 dans le forum C++
    Réponses: 2
    Dernier message: 09/11/2005, 17h21
  4. Comment l'appel à une méthode virtuelle....
    Par Blobette dans le forum C++
    Réponses: 7
    Dernier message: 07/12/2004, 13h55
  5. [C#] Méthode virtuelle
    Par jacma dans le forum Windows Forms
    Réponses: 4
    Dernier message: 07/11/2004, 08h18

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