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 :

Heritage et méthode virtuelle dans constructeur


Sujet :

C++

  1. #1
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2011
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2011
    Messages : 12
    Points : 10
    Points
    10
    Par défaut Heritage et méthode virtuelle dans constructeur
    Bonjour à tous,

    Je bloque sur un problème de conception pour appeler une méthode virtuelle depuis un constructeur. Voici mon cas de figure :
    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
     
    class mere:
    {
    public:
    	mere()
    	{
    		display();
    	}
     
    	virtual void display()
    	{
    		cout<<"mere::display()"<<endl;
    	}
    }
     
    class fille: public mere
    {
    public:
    	fille():mere()
    	{
    	}
     
    	void display()
    	{
    		cout<<"fille::display()"<<endl;
    	}
    }
    J'ai plusieurs filles et dans certaines la méthode display() est redéfinit.
    Cependant c'est toujours la méthode mere::display() qui est appelée.

    Une solution (lourde) serait d'appeler this->display() dans chaque constructeur fille mais n'y a-t-il pas quelquechose de plus propre ?

    merci par avance.

  2. #2
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Le fonction membre appelée sera toujours celle de la classe mère.
    Lis ce tutoriel sur les fonctions virtuelles en C++.

  3. #3
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Effectivement. Dit autrement, la vtable n'est pas opérationnelle dans le constructeur, on ne peut donc pas utilser l'héritage de fonctions dans celui-ci.

    Une solution possible consiste à créer la classe en 2 temps, avec une fonction Create (ou Build, ou Init, ...):
    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
    struct Mere
    {
    	void Create() { Bar(); }
    	virtual void Bar() = 0;
    };
     
    struct Fille : public Mere
    {
    	void Bar() { cout << "fille" << endl; }
    };
     
    int main(int argc, char* argv[])
    {
    	Fille fille;
    	fille.Create();
     
    	return 0;
    }
    D'autres solutions sont possibles, en utilisant une factory par exemple, mais ça dépend de ce que tu veux faire exactement.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  4. #4
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2011
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2011
    Messages : 12
    Points : 10
    Points
    10
    Par défaut
    @Steph_ng8 merci pour le lien, ça correspond exactement à mon probleme.. malheureusement !
    @r0d en fait c'est quasiement ce que je fais à l'heure actuelle. J'ai enlevé display() du constructeur de ma classe mere et pour chaque fille je fais :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class fille: public mere
    {
    public:
    	fille():mere()
    	{
    	}
     
    	void display()
    	{
    		mere::display();
    		//blabla...
    	}
    }
    Je trouvais cette méthode un peu lourde mais visiblement je n'ai pas trop le choix.
    Mon cas se rapproche pas mal de factory. Tu dis qu'il y a des solutions plus spécifique à ce pattern ?

    Merci par avance.

  5. #5
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Le problème c'est qeu ça dépend vraiment de ce que tu veux faire.
    Par exemple, si la fonction que tu veux appeler dans le constructeur ne modifie pas ta classe, alors tu peux utiliser this et ça change tout.
    Si c'est vraiment un problème de construction de la classe, ça dépend de comment sont faites des classes.
    Si c'est un problème de comportement, il faut passer par un Template Method.
    Si ce sont des classes avec sémantique de valeur, alors peut-être que la solution sera de passer par des templates.
    etc...
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  6. #6
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2011
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2011
    Messages : 12
    Points : 10
    Points
    10
    Par défaut
    Bon ok, je vais détailler un peu alors.

    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
     
    class mere:
    {
    public:
    	mere()
    	{
    		_table = new TABLE();
    	}
     
    	virtual void display()
    	{
    		_table->Remplir();
    		_table->Afficher();
    	}
    protected:
    	TABLE* _table;
    }
     
    class fille: public mere
    {
    public:
    	fille():mere()
    	{
    		this->display();
    	}
     
    	void display()
    	{
    		//on affiche _table entièrement
    		mere::display();
     
    		//comportement particulier spécifique à fille::display()
    		_table->CacheColonne(1);
    	}
    }
    En fait ces classes servent à remplir et afficher une table provenant d'une base de donnée, ceci est fait via display(). Mes classes filles peuvent avoir un comportement plus spécifique.
    Mais dans ces cas là je suis obligé d'appeler à chaque fois display() dans le constructeur de fille.

    J'aurais pensé pouvoir le mettre dans la classe mere et avoir le bon comportement a chaque fois.

  7. #7
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par nspxroronoa Voir le message
    je suis obligé d'appeler à chaque fois display() dans le constructeur de fille.
    Oui mais ça ce n'est pas bon du tout.
    Une fonction ne doit faire qu'une seule chose, c'est un principe de base du développement logiciel. Un constructeur construit, une fonction display() affiche. Un constructeur ne doit rien faire d'autre de construire l'instance.
    Trés sérieusement, ça n'a pas de sens que le constructeur affiche quelque chose. Il est primordial que ton code soit logique.

    Tu utilises un langage (ici le C++) qui a été pensé, conçu et implémenté selon une "philosophie". Il est primordial de suivre cette philosophie sinon tu vas te retrouver dans des problèmes impossibles à résoudre.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  8. #8
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2011
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2011
    Messages : 12
    Points : 10
    Points
    10
    Par défaut
    Oui je connais ce principe.

    J'avoue avoir usé abusivement/maladroitement du terme "affiche" : la fonction display() sert juste à remplir ma table - vide - qui est créer dans mon constructeur.

    Alors peut-être que "display" n'est pas forcément le nom le plus approprié mais il s'agit là d'une fonction d'init, je pense qu'elle a sa place dans mon constructeur dans le sens ou je l'appele forcément à la suite de la construction de ma classe.

    Bon s'il n'y a pas de solution, je vais rester sur mon dernier code.

    Merci pour ces conseils r0d.

  9. #9
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Ok.
    En fait, ce qu'il y a c'est que j'ai du mal à comprendre ce que tu cherches à faire. Est-ce que le code suivant ne pourrait pas faire l'affaire par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct Mere // je met struct à la place de class c'est juste pour ne pas avoir à ajouter 'public:'
    {
       Mere() { FillTable(); }
       FillTable() { /* rempli la table */ }
    };
     
    struct Fille : public Mere
    {
       Fille() { HideColumn(1); }
    };
    La fonction FillTable() est donc implémentée dans la classe Mere. FillTable étant appelée dans le constructeur de Mere, elle sera appelée automatiquement dans les constructeurs des Filles.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  10. #10
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2011
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2011
    Messages : 12
    Points : 10
    Points
    10
    Par défaut
    Tu as compris ce que je cherchais à faire vu le code que tu me donnes.

    C'est ce que j'avais tenté de faire au tout début. Le problème c'est que si je décide de ré-implémenter FillTable() dans une Fille, avec ce code là, elle ne sera pas appelé.

  11. #11
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Ok.
    Alors là, la solution la plus simple, c'est ce que proposais au début: passer par une fonction tierce. Dans ton cas, je me contenerais de surcharger la fonction FillTable().

    C'est sûr que ça oblige à faire un appel superflu à cette fonction. Superflu dans le sens où, puisqu'elle va être appelée à chaque fois, elle devrait pouvoir être automatisée.

    Il y a bien des façons d'éviter ça. Cela simplifiera l'utilisation de la classe, mais ça va complexifier le code de la classe elle-même, à toi de voir si cela en vaut la peine.

    L'idée consiste à déléguer la tâche que tu veux exécuter, ici remplir la table (FillTable). La solution la plus simple procède par héritage:
    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
    // supposons qu'il existe une classe Table et que le rôle de fille Table est de remplir une instance de Table
    struct TableFiller
    {
       virtual void FillTable( Table & table_to_fill ) const = 0; // Pas de NVI ici, certains te diront que c'est mal, mais pas moi
    };
     
    struct DefaultTableFiller : public TableFiller
    {
       void FillTable( Table & table_to_fill ) const { /* code qui rempli la table */ }
    };
     
    struct AutreTableFiller : public TableFiller
    {
       void FillTable( Table & table_to_fill ) const { /* code différent qui rempli la table */ }
    };
     
    struct Mere
    {
       Mere( const TableFiller * table_filler ) { table_filler->FillTable( the_table ); 
       Table the_table;
    };
     
    struct Fille : public Mere
    {
       Fille( const TableFiller * table_filler ) : Mere( filler )
       { /* code spécifique de construction de la Fille */ }
    };
     
    main()
    {
       Fille fille1( new DefaultTableFiller() );
       Fille fille2( new AutreTableFiller() );
       // ...
    }
    J'espère que tu vois l'idée, sinon j'aurais du mal à mettre ça en français

    Mais bon, il existe bien d'autres méthodes plus sophistiquées, pouvant aller jusqu'à l'utilisation d'une inplace factory de boost.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  12. #12
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Autre proposition:
    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
    #include <string>
    #include <iostream>
     
    using namespace std;
     
    struct Dum
    {
    	Dum( const string & made_in = "n/a" ) : made_in(made_in) {}
    	string made_in ;
    };
     
    struct Filler1
    {
    	static void Fill( Dum & dum ) { dum.made_in = "delegate 1"; }
    };
     
    struct Filler2
    {
    	static void Fill( Dum & dum ) { dum.made_in = "delegate 2"; }
    };
     
    template <typename T>
    struct Mere
    {
    	Mere() { T::Fill( dum ); }
    	Dum dum;
    };
     
    template <typename T>
    struct Fille : public Mere<T>
    {
    	Fille() : Mere<T>() {}
    };
     
     
    int main(int argc, char* argv[])
    {
    	Fille< Filler1 > fille1;
    	Fille< Filler2 > fille2;
     
    	cout << fille1.dum.made_in << endl;
    	cout << fille2.dum.made_in << endl;
     
    	cin.get();
    	return 0;
    }
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  13. #13
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2011
    Messages
    12
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2011
    Messages : 12
    Points : 10
    Points
    10
    Par défaut
    Merci pour tes deux propositions !

    Je ne te cache pas que la deuxième me parait plus complexe ^^.
    La première est plus intéressante pour moi, je vais surement me diriger la dessus vu que plus j'avance et plus je me dit que je vais avoir différente stratégie de remplissage.

    En tout cas merci beaucoup pour tes conseils

  14. #14
    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 nspxroronoa Voir le message
    Merci pour tes deux propositions !

    Je ne te cache pas que la deuxième me parait plus complexe ^^.
    La première est plus intéressante pour moi, je vais surement me diriger la dessus vu que plus j'avance et plus je me dit que je vais avoir différente stratégie de remplissage.

    En tout cas merci beaucoup pour tes conseils
    Salut,
    La solution à base d'héritage est plus intéressant si le type effectif de la classe de remplissage n'est connu qu'au moment de l'exécution. En revanche, si tout est fixe dès la compilation, alors autant utiliser la solution générique.

  15. #15
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    Une solution pour ton problème nspxroronoa, est effectivement une Factory qui se charge d'appeler les méthodes virtuelles après l'instanciation ou alors utilisé un pseudo constructeur : Qu'est-ce que l'idiome du constructeur nommé (Named Constructor) ?
    Dans les deux cas, tu finis l'initialisation de ton objet en dehors du constructeur !



    Citation Envoyé par Steph_ng8 Voir le message
    Le fonction membre appelée sera toujours celle de la classe mère.
    Lis ce tutoriel sur les fonctions virtuelles en C++.
    Sauf en C++Builder, si l'on hérite des TObject du Delphi, la VMT est chargée dès le début !

    Ainsi les méthodes virtuelles sont déjà disponible dès l'ancêtre !
    Ce qui est assez gênant car les membres objets en allocation statique sont par contre allouer normalement (au moment de chaque constructeur) mais accessible via RTTI depuis l'ancêtre !
    Du coup, on peut modifier dans un constructeur ancêtre mais on perd les données au moment du constructeur hérité ... pour un système d'objet persistant, c'est ballot !

    Si vous êtes curieux : Méthodes Virtuelles appelés depuis un Constructeur


    L'appel de méthode virtuelle dans un constructeur est même un mécanisme de base en Delphi (dont dépend tout le FrameWork de C++Builder), surtout qu'il existe aussi les constructeurs virtuelles ou même les méthodes de classes virtuelles, éléments manquants en C++Builder 2007 (et semble-t-il en C++ en général dont la philosophie est très différente de mes habitudes Delphi) : Pattern Strategy et Constructeurs Virtuels VCL
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

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

Discussions similaires

  1. méthode virtuelle dans héritages
    Par bobby51 dans le forum Langage
    Réponses: 4
    Dernier message: 21/02/2010, 19h36
  2. Réponses: 6
    Dernier message: 10/10/2007, 20h11
  3. Réponses: 15
    Dernier message: 05/07/2007, 01h29
  4. Réponses: 2
    Dernier message: 10/05/2007, 17h29

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