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 :

Problème : héritage et polymorphisme


Sujet :

C++

  1. #1
    Membre du Club

    Profil pro
    Inscrit en
    Juin 2005
    Messages
    52
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 52
    Points : 50
    Points
    50
    Par défaut Problème : héritage et polymorphisme
    Bonjour,

    Je débute en c++, mais je programme déjà depuis quelques années en Java.

    J'ai un problème de compilation lorsque j'appel un méthode définie dans la super classe sur une instance de la sous classe.
    Voici un extrait du 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
     
    #include <iostream>
    using namespace std;
    class A {
    public:
    	A() {
    	}
    	bool add(int & element) {
    		cout << "A::add(int)" << endl;
    		return true;
    	}
    };
     
    class B : public A {
     
    public:
    	B() : A() {
    	}
    	void add(int index, int & element) {
    		cout << "B::add(int,int)" << endl;
    	}
    };
     
    int main() {
    	B b = B();
    	int element = 6;
    	bool added = b.add(element);
    	if (added) {
    		cout << "Added" << endl;
    	} else {
    		cout << "Not added" << endl;
    	}
    	return 0;
    }
    Je compile la classe avec le compilateur de visual studio 2008 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    $ cl -c -EHsc test.cpp
    test.cpp
    test.cpp(26) : error C2660: 'B::add' : function does not take 1 arguments
    Je ne comprends pas pourquoi une instance de B ne peut pas exécuter la méthode add(int) alors qu'elle est définie dans la super classe.
    On dirait que B::add(int, int&) masque la méthode A::add(int&)

    Merci d'avance pour vos réponses.

  2. #2
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    Par défaut
    Je ne comprends pas pourquoi une instance de B ne peut pas exécuter la méthode add(int) alors qu'elle est définie dans la super classe.
    On dirait que B::add(int, int&) masque la méthode A::add(int&)
    Parce que ton instance est un B. Pour utiliser le polymorphisme, tu dois le réaliser avec un pointeur ou une référence. Ce que tu réalises actuellement, c'est un b par défaut, puis une copie d'un B temporaire dans ton b.
    Pour réalisé ton polymorphisme tu dois le faire comme suit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    A * b = new B;
    //blablablabla
    delete b;
    Nota: n'oublie pas de mettre le destructeur de A virtuel.
    Homer J. Simpson


  3. #3
    Membre du Club

    Profil pro
    Inscrit en
    Juin 2005
    Messages
    52
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 52
    Points : 50
    Points
    50
    Par défaut
    Je suis d'accord avec ta réponse, mais c'est un peu contradictoire avec la notion d’héritage.

    En effet l'héritage permet d'ajouter des nouvelles fonctionnalités à une classe or quand on a un objet B nous devrions avoir accès aux fonctionnalités de A.

    Ton exemple est correct mais cela reviens à faire :
    Or l’intérêt de faire une classe B est la possibilité d'appeler les méthodes de A
    or le code si dessous ne fonctionne pas.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    B * b = new B()
    b->add(5);

  4. #4
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Tu es en train d'expérienter le masquage des fonctions.

    Quand tu fais un appel à une fonction (lié à une classe dans notre cas) disnon a.foo(), le compilateur regarde dans le scope de la classe de l'objet a (via le type statique), si il y a une telle fonction (il regarde jsute le nom), si c'est le cas il s'arrete sinon il continue dans les classes mères (*).

    Un fois qu'il s'est arreté il regarde si une signature peut correspondre (soit directement, soit par transtypage), si c'est le cas il l'utilise (**), sinon tu as une erreur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    struct A
    { void foo() {} };
     
    struct B : A
    { void foo(int) {} };
     
    A a; B b; A* p = new B();
    a.foo(); a.foo(0); //1 compile, 2 ne compile pas
    b.foo(); b.foo(0); //1 ne compile pas [1], 2 compile
    p->foo(); p->foo(0); //[2] 1 compile, 2 ne compile pas
    [1] Le type statique de b est B, or dans la définition de la classe B il y a une fonction qui s'appelle foo, donc il s'arrete là mais la signature ne correspond pas : erreur
    [2] Je ne détaille pas, mais il ne faut pas oublier que le type statique de *p est A et non B

    (*) Je ne détaille pas, mais il y a tout une remonté progressive des scopes qui est effectuée, jusqu'à atteindre le namespace global, pour les fonctions libres, il faut rajouter d'autre méchanisme qui prend en compte le scope où sont les définitions des arguments, cf Koenig Lookup.

    (**) A ce niveau intervient le polymorphisme, qui indique au compilateur si il doit faire la liaison entre la fonction trouvé et l'appel ou si il doit laisser cette tache pour plus tard (liaison dynamique qui prend en compte le type dynamique de l'objet).

    Edit: J'oubliais le plus important, pour pallier à ceci tu peux indiquer au compilateur de prendre en compte les fonctions de la classes mère via une instruction using :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    struct A
    { void foo() {} };
     
    struct B : A
    { using A::foo; void foo(int) {} };

  5. #5
    Membre du Club

    Profil pro
    Inscrit en
    Juin 2005
    Messages
    52
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 52
    Points : 50
    Points
    50
    Par défaut
    Ok pour la clause using ça reponds à mon attente mais pourquoi devoir le dire explicitement ?...

    En Java, quand on définie des méthodes publiques dans une super classes celles-ci sont disponibles dans la classe fille.

    Je pensais que c'était la même chose en C++

  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
    C'est le cas ... si tu ne les masques pas

  7. #7
    Membre du Club

    Profil pro
    Inscrit en
    Juin 2005
    Messages
    52
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 52
    Points : 50
    Points
    50
    Par défaut
    Pour plus d'informations : http://cpp.developpez.com/faq/cpp/?p...CLASS_masquage

    En tout cas merci à tous pour vos réponses claires.
    Je ne savais pas que ce mécanisme s'appelait le masquage

  8. #8
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    La FaQ ne contient pas plus d'information que ce que je t'ai expliqué si ce n'est la technique pour l'appel explicite avec l'opérateur de résolution de portée.

    Si tu te demandes pourquoi c'est comme, pourquoi le compilateur ne remonte pas d'office la hiérarchie, il me semble que Herb Sutter l'explique dans un artcile qui traite partiellement du sujet (globalement il doit traiter de savoir ce qui fait partie ou non de l'interface d'une classe, cf GotW).

  9. #9
    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
    Citation Envoyé par Flob90 Voir le message
    Si tu te demandes pourquoi c'est comme, pourquoi le compilateur ne remonte pas d'office la hiérarchie, il me semble que Herb Sutter l'explique dans un artcile qui traite partiellement du sujet (globalement il doit traiter de savoir ce qui fait partie ou non de l'interface d'une classe, cf GotW).
    Ceci dit je ne trouve pas cela très 'naturel' non plus. De même que si la classe de base possède plusieurs signatures virtuelles et que la classe dérivée n'en spécialise qu'une alors les autres sont masqué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
    struct base {
       virtual void f();
       virtual void f(int);
    };
     
    struct derive : public base
    {
       virtual void f(int);
    };
     
     
    int main()
    {
       derive d;
       d.f(); // erreur
       return 0;
    }
    C'est pas très intuitif. Faudra que je cherche le gotw que tu mentionnes pour voir comment c'est justifié.

    Autant redéfinir dans la classe dérivée une fonction non virtuelle de la classe de base me semble être probablement un problème de design. Autant ne pas spécialiser toutes les signatures peut avoir un sens.

    Bref, on ne rappelera jamais assez tous les avantages du NVI

    Sur le masquage : Fonction virtuelle et masquage de fonction

  10. #10
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    J'avais jamais fait attention au cas des surcharges de fonctions virtuelles, c'est vraie que dans ce cas c'est assez surprenant

    Mais je ne suis pas certain que ce soit si handicapant, si deux fonctions portent le même noms c'est qu'elles ont un comportement similaire, donc la nécessité de modifier le comportement d'une seule dans une classe fille devrait, assez souvent, créer une nécessité de modifier les autres surcharges pour qu'elles gardent une cohésion entre elles.

    C'est cet article http://www.gotw.ca/publications/mill08.htm note de bas de page numéro 3 (l'article traite de quelque chose de plus global : les namespaces, mais c'est bien la situation dans laquelle on se retrouve si on considère qu'il faut à chaque fois remonter dans le scope précédent).

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

Discussions similaires

  1. Problème de mise en oeuvre de l'héritage et polymorphisme
    Par jeanjean6 dans le forum Débuter avec Java
    Réponses: 4
    Dernier message: 24/05/2010, 20h35
  2. Problème avec le polymorphisme
    Par Vitis_Alba dans le forum C++
    Réponses: 14
    Dernier message: 15/01/2007, 16h41
  3. [POO] Problème héritage des classes PHP4
    Par zana74 dans le forum Langage
    Réponses: 2
    Dernier message: 15/08/2006, 16h00
  4. Problème Héritage JavaScript
    Par Flavien dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 10/07/2006, 13h29
  5. Problème héritage CWnd
    Par ptitJP dans le forum MFC
    Réponses: 4
    Dernier message: 21/04/2006, 11h41

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