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 :

Nécessité de définir un constructeur de copie


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut Nécessité de définir un constructeur de copie
    Bonjour,

    Je me pose la question sur la "nécessité" de définir un constructeur de copie dans le cas suivant.

    Supposons que l'on ait une classe C utilisée dans une classe Test telles que:

    C.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    C::C()
    {
    }
     
    C::C(double a, double b)
    {
      m_a = a;
      m_b = b;
    }

    Test.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Test::Test() : m_C()
    {
    }
     
    void Test::someFunction(double a, double b)
    {
      m_C = C(a, b);
    }
    main.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Test t = Test();
    t.someFunction(3.14, 1.51);
    Dois-je définir un constructeur de copie :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    C::C(const &C copy)
    {...}
    Ou cela n'est pas nécessaire?

    Merci de votre aide.

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

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

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Bonjour,

    Dans ton cas ce n'est pas le constructeur par copie qui est appeler mais l'opérateur =.

    Ton instance C est créer ici :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test() : m_C()
    {
    }
    Il appelle donc le constructeur sans paramètre ( Ici ce n'est pas nécessaire si ton constructeur n'a pas de paramètre --> m_C() )

    Ton affectation :
    Ici le compilo va appeler le constructeur paramétrique de C puis operator= et pas le constructeur par copie car ton C est déjà instancié dans ton constructeur de Test

  3. #3
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Citation Envoyé par Astraya Voir le message
    Dans ton cas ce n'est pas le constructeur par copie qui est appeler mais l'opérateur =.
    Justement, c'était ça qui me travaillait et ce dont je n'étais pas sûr.
    Peux-tu m'expliquer précisement pourquoi (hormis le fait que l'instance est déjà crée)?

  4. #4
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

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

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Et bien tu appelles le constructeur par copie à la .... je te le donne en mille -> construction

    Un exemple de construction par copie (quoi que optimisable par le compilo je pense...) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test() : m_C(C(a, b))
    {
    }
    Un exemple d'affection : Ton exemple

    Pour rejoindre Koala je connais ça sous le nom de sainte trinité : constructeur de copie, affectation, destructeur.

  5. #5
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Hello !

    Dans ton cas, ce n'est pas nécessaire, le constructeur par copie par défaut généré par le compilateur et l'opérateur d'affectation seront corrects. C'est dans la FAQ.

    Attention, tes constructeurs ne sont pas bien rédigés, préfère:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    C::C() : m_a(0), m_b(0) {}  // Ne pas oublier d'init les membres ! En C++11, on peut le faire dans le header
    C::C(double a, double b) : m_a(a), m_b(b) {}
    Test::Test() {}  // Pas besoin de construire explictement le membre m_C

  6. #6
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Citation Envoyé par jblecanard Voir le message
    Attention, tes constructeurs ne sont pas bien rédigés, préfère:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    C::C() : m_a(0), m_b(0) {}  // Ne pas oublier d'init les membres ! En C++11, on peut le faire dans le header
    C::C(double a, double b) : m_a(a), m_b(b) {}
    Test::Test() {}  // Pas besoin de construire explictement le membre m_C
    Oui tu as raison ,merci.


    Par contre, est-ce une mauvaison pratique de construire par defaut le membre m_C, puis lui affecter une valeur?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test() : m_C(){}
     
    m_C = C(...);
    Car la classe Test peut contenir d'autres membres qui seront initialisés et il me semble qu'il vaut mieux initialiser tout les membres dans une liste d'initialisation.

    En gros, qu'est-il préférable de faire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test() : m_A(), m_B(), m_D(){}
     
    m_C = C(...);
    ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test() : m_A(), m_B(), m_C(), m_D(){}
     
    m_C = C(...);

  7. #7
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

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

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Alors si tu veux que Test instancie tes membres avec des paramètres utilise la liste d'initialisation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Test::Test(double a, double b): m_C(a,b)
    Ici le compilateur va construire m_C avant Test car c'est le principe de la liste d'initialisation .
    Puis il construit Test avec m_C qui est déjà créer, cela évite tes copie/affectation etc....

    EDIT:
    Sinon :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Test::Test(const C& c): m_C(c)
    ici il appelle le constructeur par copie de C et suit le même schéma que l'exemple précédent.

  8. #8
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Citation Envoyé par Astraya Voir le message
    Alors si tu veux que Test instancie tes membres avec des paramètres utilise la liste d'initialisation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Test::Test(double a, double b): m_C(a,b)
    Ici le compilateur va construire m_C avant Test car c'est le principe de la liste d'initialisation .
    Puis il construit Test avec m_C qui est déjà créer, cela évite tes copie/affectation etc....

    EDIT:
    Sinon :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Test::Test(const C& c): m_C(c)
    ici il appelle le constructeur par copie de C et suit le même schéma que l'exemple précédent.
    Mais si au moment d'apppeler le constructeur de Test() on a pas encore les valeurs a, b ou l'objet pour le passer au constructeur par copie?

  9. #9
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Citation Envoyé par LinuxUser Voir le message
    Mais si au moment d'apppeler le constructeur de Test() on a pas encore les valeurs a, b ou l'objet pour le passer au constructeur par copie?
    Bah c'est pas grave, tu affectes plus tard ! Le plus important, c'est de ne pas avoir de mémoire non initialisée qui traîne.

  10. #10
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Citation Envoyé par LinuxUser Voir le message
    Par contre, est-ce une mauvaison pratique de construire par defaut le membre m_C, puis lui affecter une valuer?
    Ca dépend !

    Si l'affectation de valeur se passe dans une fonction membre, tu n'as pas vraiment le choix.

    Si l'affectation a lieu dans le code du constructeur, dans la plupart des cas, c'est possible de le faire dans la liste d'init :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Test::Test() : m_C(1,0) {}
    Test::Test() : m_C(fonction_qui_renvoieun_object_C()) {}
    Il n'y a pas de règle sacro-sainte. Il vaut mieux ne pas briser les conventions par plaisir ou anticonformisme aveugle, mais si la résolution de ton problème le requiert, ce n'est pas un souci.

  11. #11
    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
    Citation Envoyé par LinuxUser Voir le message
    Bonjour,

    Je me pose la question sur la "nécessité" de définir un constructeur de copie dans le cas suivant.

    Supposons que l'on ait une classe C utilisée dans une classe Test telles que:

    C.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    C::C()
    {
    }
     
    C::C(double a, double b)
    {
      m_a = a;
      m_b = b;
    }

    Test.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test() : m_C()
    {
    }
    Déjà, on préférera la syntaxe de Test à la syntaxe de C, car les listes d'initialisation sont, de manière générale, plus efficaces et permettent, surtout, d'éviter certaines limitations imposées par l'affectation (entre autres, lorsque ta classe n'est pas copiable), et ce, même pour les types primitifs
    Dois-je définir un constructeur de copie :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    C::C(const &C copy)
    {...}
    Ou cela n'est pas nécessaire?

    Merci de votre aide.
    En fait, le constructeur par copie, l'opérateur d'affectation et le destructeur sont intimement liés.

    A telle point qu'il existe une règle spécifique, nommée "règle des trois grands" qui te donne la réponse à ta question:

    Si tu dois (par exemple, pour libérer une ressource pour laquelle tu as eu recours à l'allocation dynamique de la mémoire) définir toi meme le destructeur de ta classe, alors tu devras également définir le constructeur par copie et l'opérateur d'affectation.

    A l'inverse, cela signifie que si tu ne dois pas définir ton propre destructeur, tu ne dois pas non plus définir ni le constructeur par copie ni l'opérateur d'affectation

    Le compilateur C++ est en effet en mesure de donner un comportement "naïf" à ces trois fonctions en:
    • appelant le constructeur par copie de chaque membre dans l'ordre de leur déclaration dans le constructeur par copie de ta classe
    • assignant chaque membre de ta classe (dans l'ordre de leur déclaration toujours) dans l'opérateur d'affectation
    • appelant le destructeur de chaque membre de ta classe dans l'ordre inverse de leur déclaration dans le destructeur.
    Si ce comportement "naïf" te suffit, tu n'as strictement à t'inquiéter d'aucun de ces trois fonctions particulières.

    Si, par contre, ce comportement ne te suffit pas (et le premier endroit où tu prendras sans doute conscience que c'est le cas est le destructeur), alors, il faudra donner une implémentation personnelle pour les trois
    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

  12. #12
    Membre éclairé Avatar de LinuxUser
    Inscrit en
    Avril 2007
    Messages
    857
    Détails du profil
    Informations forums :
    Inscription : Avril 2007
    Messages : 857
    Par défaut
    Citation Envoyé par koala01 Voir le message

    A telle point qu'il existe une règle spécifique, nommée "règle des trois grands" qui te donne la réponse à ta question:

    Si tu dois (par exemple, pour libérer une ressource pour laquelle tu as eu recours à l'allocation dynamique de la mémoire) définir toi meme le destructeur de ta classe, alors tu devras également définir le constructeur par copie et l'opérateur d'affectation.

    A l'inverse, cela signifie que si tu ne dois pas définir ton propre destructeur, tu ne dois pas non plus définir ni le constructeur par copie ni l'opérateur d'affectation

    Le compilateur C++ est en effet en mesure de donner un comportement "naïf" à ces trois fonctions en:
    • appelant le constructeur par copie de chaque membre dans l'ordre de leur déclaration dans le constructeur par copie de ta classe
    • assignant chaque membre de ta classe (dans l'ordre de leur déclaration toujours) dans l'opérateur d'affectation
    • appelant le destructeur de chaque membre de ta classe dans l'ordre inverse de leur déclaration dans le destructeur.
    Si ce comportement "naïf" te suffit, tu n'as strictement à t'inquiéter d'aucun de ces trois fonctions particulières.
    Merci beaucoup koala01, c'est très clair

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

Discussions similaires

  1. Réponses: 3
    Dernier message: 24/04/2005, 14h19
  2. [C++]Heritage et constructeur de copie
    Par matazz dans le forum C++
    Réponses: 2
    Dernier message: 25/03/2005, 12h31
  3. Constructeur de copie modifiant le paramètre ?
    Par Nicodemus dans le forum C++
    Réponses: 4
    Dernier message: 12/01/2005, 21h25
  4. Constructeur de copie et Template: Transtypage
    Par ikkyu_os dans le forum Langage
    Réponses: 9
    Dernier message: 26/12/2004, 22h29

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