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 :

constructeur de recopie et héritage


Sujet :

C++

  1. #1
    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 constructeur de recopie et héritage
    Bonjour à tous,

    j'ai trois choses:
    1 structure S:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    typedef struct
    {
    int a,b;} S;
    une classe Mere, dont le constructeur prend un paramètre de type S et qui comporte un membre de type S:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Mere:
    {
    public:
        Mere(S s){m_s = s};
        virtual ~Mere(){};
     
    protected:
        S m_s;
    };
    et une classe Fille, qui nécessite un constructeur de recopie:
    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
    {
        Fille(S s);
        Fille(Fille &source); //le constructeur de recopie
        ~Fille();
    };
     
    // Fille.cpp :
    Fille::Fille(S s) : Mere(s){}
     
    Fille::Fille(Fille &source)
    {
        m_s = source.m_s;
    }
    Et bien évidemment, ça ne marche pas. J'obtiens l'erreur suivante:
    error C2512: 'Mere' : no appropriate default constructor available

    Il y a quelque chose qui m'échappe concernant ces constructeurs par défaut et de recopie. Pourquoi ici il me demande un constructeur par defaut pour ma classe Mere? Pourquoi en a-t-il besoin?
    « 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

  2. #2
    Membre confirmé
    Profil pro
    Inscrit en
    Novembre 2003
    Messages
    394
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2003
    Messages : 394
    Points : 473
    Points
    473
    Par défaut
    Deux choses pour comprendre :
    - La première chose que fait le constructeur d'une classe dérivée c'est d'appeler un constructeur de la classe dont elle dérive. Si aucun constructeur n'est précisé, c'est le constructeur qui ne prend aucun paramètre qui est appelé

    - Si aucun constructeur n'est déclaré dans une classe, alors le compilateur crée un constructeur par défaut qu ne prend aucun paramètre. S'il existe au moins un constructeur, aucun constructeur par défaut n'est automatiquement créé.

    Dans ton cas, l'erreur vient du fait que
    A) Le constructeur de Mère appelé lors de l'appel du constructeur de Fille est le constructeur qui ne prend aucun paramètre (puisque tu n'en précise aucun autre)

    B) Ce constructeur sans paramètre n'existe pas dans la classe Mère.

    EDIT: C'est bien sûr pour ton constructeur de recopie que le problème apparaît

  3. #3
    Membre averti Avatar de Rafy
    Profil pro
    Inscrit en
    Juillet 2005
    Messages
    415
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France

    Informations forums :
    Inscription : Juillet 2005
    Messages : 415
    Points : 417
    Points
    417
    Par défaut
    En fait il n'y a pas grand chose à changer dans 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
    class Fille : public Mere 
    { 
        Fille(S s); 
        Fille(Fille &source); //le constructeur de recopie 
        ~Fille(); 
    }; 
     
    // Fille.cpp : 
    Fille::Fille(S s) : Mere(s){} 
     
    Fille::Fille(Fille &source) :
    Mere(source.m_s)
    { 
     
    }
    Avec ça ça devrait marcher.
    Première grosse démo en construction :
    http://bitbucket.org/rafy/exo2/

  4. #4
    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 Rafy
    En fait il n'y a pas grand chose à changer dans 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
    class Fille : public Mere 
    { 
        Fille(S s); 
        Fille(Fille &source); //le constructeur de recopie 
        ~Fille(); 
    }; 
     
    // Fille.cpp : 
    Fille::Fille(S s) : Mere(s){} 
     
    Fille::Fille(Fille &source) :
    Mere(source.m_s)
    { 
     
    }
    Avec ça ça devrait marcher.
    En effet, ça marche. Mais, malgré vos brillantes explications (pour lesquelles je m'incline bien bas et vous bennis, vous et votre déscendance sur 7 générations ), mais il subsiste un point sur lequel je bloque:
    D'après ce que vous me dites, le constructeur de recopie, s'il est implémenté comme cela:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Fille::Fille(Fille &source)
    {
        m_s = source.m_s;
    }
    appelle le constructeur par défaut de la classe Mere. C'est cela que je ne comprends pas. D'une part: pourquoi, et par quel sombre et tortueux processus du compilateur c++, en est-il ainsi?
    Et d'autre part, pourquoi, sous la forme proposée par Rafy, il n'en est rien?


    Encore
    « 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

  5. #5
    Rédacteur
    Avatar de Laurent Gomila
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2003
    Messages
    10 651
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2003
    Messages : 10 651
    Points : 15 920
    Points
    15 920
    Par défaut
    Parce qu'avec ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Fille::Fille(Fille &source)
    {
        m_s = source.m_s;
    }
    Le compilo te génère ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Fille::Fille(Fille &source) : Mere()
    {
        m_s = source.m_s;
    }
    Pourquoi ? VoidSeer te l'a dit, le constructeur d'une classe dérivée commence toujours par appeler celui de la classe mère. Donc si tu ne le précises pas, le compilo le fera pour toi, et utilisera forcément le constructeur par défaut. Si par contre tu le fais explicitement, comme dans la solution, alors le compilo sera content et ne cherchera plus à appeler le constructeur par défaut de la classe mère.

  6. #6
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Quelques remarques supplémentaires...

    Premièrement, ta classe mère un un destructeur virtuel. Cela signifie que tu veux manipuler les instances de cette hiérarchie de manière polymorphe. Mais d'autre part, tu t'occupes des constructeurs de copie. Les deux vont rarement ensemble d'après mon expérience. Le seul cas que je vois est l'idiome plutôt particulier de lettre/enveloppe et personnellement je préfère les alternatives.

    Deuxièmement, il y a 4 membres déclarés et définis implicitement (de façon inline) dans certains cas: le constructeur par défaut s'il n'y a aucun constructeur explicitement déclaré, un constructeur de copie s'il n'y a aucun constructeur de copie , un opérateur d'assignement s'il ne l'est pas et le destructeur s'il ne l'est pas. Mere a donc un constructeur de copie et il serait vraissemblablement mieux de l'appeler:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Fille::Fille(Fille const& other) 
       : Mere(other)
    {
       m_s = other.m_s;
    }
    Quatrièmement, puisque le constructeur de Mere va s'occuper de la copie, il est inutile de le refaire dans le constructeur de Fille.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Fille::Fille(Fille const& other) 
       : Mere(other)
    {
    }
    Cinquièmement, c'est exactement ce que fait le constructeur de copie de Fille qui aurait été déclaré et défini par défaut. On peut vouloir faire ça pour s'assurer que le constructeur n'est pas inline.

    Sixièmement, quand on veut initialiser des membres, on le fait plutôt avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Mere::Mere(S const& s)
       : m_s(s)
    {
    }
    qu'avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Mere::Mere(S const& s)
    {
       m_s = s;
    }
    parce que la deuxième forme construit par défaut le membre et ensuite l'assigne tandis que la première forme permet de choisir le constructeur utilisé.

    Septièmement, y a-t'il une bonne raison pour laquelle ton constructeur de copie de Fille prend une référence non constante en paramètre?
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

  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 Loulou24
    Pourquoi ? VoidSeer te l'a dit, le constructeur d'une classe dérivée commence toujours par appeler celui de la classe mère.
    C'était cela que je ne comprenais pas: un constructeur, quel qu'il soit, appelle automatiquement le constructeur par défaut de la classe mère.

    Citation Envoyé par Jean-Marc.Bourguet
    Premièrement, ta classe mère un un destructeur virtuel. Cela signifie que tu veux manipuler les instances de cette hiérarchie de manière polymorphe. Mais d'autre part, tu t'occupes des constructeurs de copie. Les deux vont rarement ensemble d'après mon expérience. Le seul cas que je vois est l'idiome plutôt particulier de lettre/enveloppe et personnellement je préfère les alternatives.
    Alors voilà, j'ai utilisé cette méthode car j'essaie d'implémenter quelque chose, et je n'ai pas trouvé mieux. Voici ce que je voudrais faire:
    imaginons que nous ayons 2 classes, Fille1 et Fille2 qui héritent de la classe Mere. Je voudrais, dans une autre classe (que je noterais Agreg), avoir un vecteur de classe Fille. Mais je ne sais pas à l'avance si je vais avoir des Fille1 ou des Fille2. Or, pour utiliser la méthode Add (ou Insert) du template vector, mes classes FilleX doivent avoir un constructeur de recopie. Je voudrais avoir quelque chose comme suit:
    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
    class Agreg
    {
    public:
        ...
        DWORD AddFille(int FilleID, S s);
     
    private:
        vector <Mere*> monVecteur; //Bon en fait, j'utilise la mfc, donc CArray à la place du vector, mais je crois que c'est exactement pareil
    };
     
    // Agreg.cpp :
    ...
    DWORD Agreg::AddFille(int FilleID, S s)
    {
        DWORD dwRet = -1;
        Mere* pFille;
     
        switch (FilleID)
        {
        case 1:
            pFille = new Fille1(s);
            dwRet = monVecteur.Add(pFille);
            break;
     
        case 2:
            pFille = new Fille2(s);
            dwRet = monVecteur.Add(pFille2);
            break;
        }
     
        return dwRet;
    }
    Vous voyez ce que je veux faire?
    Si vous avez une meilleure solution, j'achète direk

    Citation Envoyé par Jean-Marc.Bourguet
    Quatrièmement, puisque le constructeur de Mere va s'occuper de la copie, il est inutile de le refaire dans le constructeur de Fille.
    Oui mais en vérité, mes classes filles ont des membres que ne possède pas la classe mère. Je serais donc obligé de mettre un peu de code dans le constructeur de recopie des classes filles.


    Citation Envoyé par Jean-Marc.Bourguet
    Septièmement, y a-t'il une bonne raison pour laquelle ton constructeur de copie de Fille prend une référence non constante en paramètre?
    Euh non Y aurait-il une bonne raison pour qu'il prenne une constante en paramètre?

    Pour le reste, c'est ok, j'en prend bonne note. pour ton intervention éclairée
    « 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
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Citation Envoyé par r0d
    Citation Envoyé par Loulou24
    Pourquoi ? VoidSeer te l'a dit, le constructeur d'une classe dérivée commence toujours par appeler celui de la classe mère.
    C'était cela que je ne comprenais pas: un constructeur, quel qu'il soit, appelle automatiquement le constructeur par défaut de la classe mère.
    Un constructeur appelle toujours un contructeur de la classe mère. Si on spécifie lequel, c'est celui qu'on a spécifié. Si on n'en spécifie pas, c'est celui par défaut. Si celui par défaut n'existe pas ou n'est pas accessible, ça passe pas la compilation

    Citation Envoyé par Jean-Marc.Bourguet
    Premièrement, ta classe mère un un destructeur virtuel. Cela signifie que tu veux manipuler les instances de cette hiérarchie de manière polymorphe. Mais d'autre part, tu t'occupes des constructeurs de copie. Les deux vont rarement ensemble d'après mon expérience. Le seul cas que je vois est l'idiome plutôt particulier de lettre/enveloppe et personnellement je préfère les alternatives.
    Alors voilà, j'ai utilisé cette méthode car j'essaie d'implémenter quelque chose, et je n'ai pas trouvé mieux. Voici ce que je voudrais faire:
    imaginons que nous ayons 2 classes, Fille1 et Fille2 qui héritent de la classe Mere. Je voudrais, dans une autre classe (que je noterais Agreg), avoir un vecteur de classe Fille. Mais je ne sais pas à l'avance si je vais avoir des Fille1 ou des Fille2. Or, pour utiliser la méthode Add (ou Insert) du template vector, mes classes FilleX doivent avoir un constructeur de recopie. Je voudrais avoir quelque chose comme suit:
    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
    class Agreg
    {
    public:
        ...
        DWORD AddFille(int FilleID, S s);
     
    private:
        vector <Mere*> monVecteur; //Bon en fait, j'utilise la mfc, donc CArray à la place du vector, mais je crois que c'est exactement pareil
    };
     
    // Agreg.cpp :
    ...
    DWORD Agreg::AddFille(int FilleID, S s)
    {
        DWORD dwRet = -1;
        Mere* pFille;
     
        switch (FilleID)
        {
        case 1:
            pFille = new Fille1(s);
            dwRet = monVecteur.Add(pFille);
            break;
     
        case 2:
            pFille = new Fille2(s);
            dwRet = monVecteur.Add(pFille2);
            break;
        }
     
        return dwRet;
    }
    Vous voyez ce que je veux faire?
    Si vous avez une meilleure solution, j'achète direk
    Tu as un vecteur de pointeurs. C'est les pointeurs qui doivent avoir un constructeur de copie, et ils l'ont. La classe pointée n'en a pas besoin.

    Citation Envoyé par Jean-Marc.Bourguet
    Quatrièmement, puisque le constructeur de Mere va s'occuper de la copie, il est inutile de le refaire dans le constructeur de Fille.
    Oui mais en vérité, mes classes filles ont des membres que ne possède pas la classe mère. Je serais donc obligé de mettre un peu de code dans le constructeur de recopie des classes filles.
    Oui, mais il ne doit pas s'occuper des membres de la mère.

    Citation Envoyé par Jean-Marc.Bourguet
    Septièmement, y a-t'il une bonne raison pour laquelle ton constructeur de copie de Fille prend une référence non constante en paramètre?
    Euh non Y aurait-il une bonne raison pour qu'il prenne une constante en paramètre?
    Pouvoir copier des objets temporaires, pouvoir copier des objets constants.

    Note: un constructeur de copie prends toujours des références en paramètre, sinon on a une récursion infinie car le passage par valeur entraine l'appel du constructeur de copie.
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

  9. #9
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Le switch se remplace normalement par une fonction de clonage (virtuelle) qui s'occupera d'appeler le constructeur de recopie idoine.

    Et sinon en général, en cas de hiérarchies polymorphes, je vais plus loin en passant protégé les constructeurs de recopie et en interdisant explicitement l'opérateur d'affectation. Cela limite les surprises.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  10. #10
    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 Luc Hermitte
    Le switch se remplace normalement par une fonction de clonage (virtuelle) qui s'occupera d'appeler le constructeur de recopie idoine.
    Pourrais-tu être un peu plus explicite s'il te plait? Je ne vois pas trop la différence entre une fonction de clonage et un constructeur de recopie.
    « 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

  11. #11
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Un constructeur de recopie ne peut pas être virtuel. cf la feinte à laquelle tu procèdes pour réaliser la duplication. L'approche idiomatique est de passer par une focntion de clonage redéfinie (qui fait appel aux constructeurs de recopie finaux). Ce doit être traité dans la FAQ normalement.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  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
    Exact, c'est décrit ici: http://c.developpez.com/faq/cpp/?pag...es#CLASS_clone
    merci à tous.
    « 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

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

Discussions similaires

  1. question constructeur recopie et héritage
    Par loicounet dans le forum C++
    Réponses: 5
    Dernier message: 26/02/2009, 12h01
  2. QObject et constructeur de recopie
    Par ionnn dans le forum Qt
    Réponses: 12
    Dernier message: 07/01/2006, 11h50
  3. Constructeur par recopie
    Par KernelControl dans le forum C++
    Réponses: 2
    Dernier message: 29/12/2005, 12h24
  4. constructeur de recopie:question theorique
    Par voyageur dans le forum C++
    Réponses: 7
    Dernier message: 02/12/2004, 22h46
  5. Constructeur par recopie
    Par sdebrois dans le forum Composants VCL
    Réponses: 13
    Dernier message: 21/10/2004, 14h47

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