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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre habitué
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    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 chevronné
    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 : 37
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2004
    Messages : 283
    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
    Membre habitué
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    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 Expert
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    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 Expert
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    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
    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
    Membre habitué
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 10
    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
    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,

    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 : 33
    Localisation : Suisse

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

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    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)

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