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

Langage C++ Discussion :

clone(), c'est le mal (?)


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut clone(), c'est le mal (?)
    J'ai eu une discussion intéressante avec des collègues ce midi: pour ou contre la fonction clone(). Elle n'est certes pas d'une importance capitale, mais soulève quelques problématiques intéressantes je trouve.

    Moi je suis contre car:
    0. Bêtement et concrètement, je n'en ai jamais eu besoin.
    1. Il vaut mieux implémenter un constructeur par copie.
    2. On ne peut pas faire du NVI si on a du clone() sur des classes destinées à être héritées.
    3. clone() peut être utile, justement, dans le but d'utiliser le polymorphisme. Mais dans ce cas, ne vaut-il pas mieux utiliser une abstract factory?

    Qu'en pensez-vous?

    Cordialement,
    r0d.

  2. #2
    Expert confirmé

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Par défaut
    Citation Envoyé par r0d Voir le message
    J'ai eu une discussion intéressante avec des collègues ce midi: pour ou contre la fonction clone(). Elle n'est certes pas d'une importance capitale, mais soulève quelques problématiques intéressantes je trouve.

    Moi je suis contre car:
    0. Bêtement et concrètement, je n'en ai jamais eu besoin.
    C'est un argument qui n'emporte pas grand chose.

    1. Il vaut mieux implémenter un constructeur par copie.
    C'est une affirmation. Les deux me semblent bien different a moi, ou plutot le clone() est un constructeur de copie polymorphique. Donc si tu as une semantique de valeur, tu as generalement un constructeur de copie, pas de polymorphisme et pas de clone. Si tu as une semantique d'entite, tu n'as generalement pas de constructeur de copie (ou du moins protected) et tu peux avoir un close si la copie fait quand meme du sens.

    2. On ne peut pas faire du NVI si on a du clone() sur des classes destinées à être héritées.
    Explique ton raisonnement, je vais finir par croire qu'on entend des choses differentes par clone. Clone n'a du sens que sur une classe destinee a etre heritee et je ne vois pas de probleme avec le NVI.

    3. clone() peut être utile, justement, dans le but d'utiliser le polymorphisme. Mais dans ce cas, ne vaut-il pas mieux utiliser une abstract factory?
    Ca me semble independant. Et je vois bien clone utilisé par un certain type d'abstract factory pour son fonctionnement.

  3. #3
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Jean-Marc.Bourguet Voir le message
    C'est un argument qui n'emporte pas grand chose.
    C'était plus une tentative d'humour qu'un argument. Passons.

    Citation Envoyé par Jean-Marc.Bourguet Voir le message
    Explique ton raisonnement, je vais finir par croire qu'on entend des choses differentes par clone. Clone n'a du sens que sur une classe destinee a etre heritee et je ne vois pas de probleme avec le NVI.
    Je me trompe sans doute, et c'est la raison pour laquelle j'ai ouvert cette discussion. Mais il me semble que le clone() de la classe mère va, généralement, être virtuelle pure. On ne peut pas cloner un objet si on ne sais pas exactement ce qu'il contient; on ne peut pas faire de "clonage partiel", ou alors ce ne serait plus un clone. Et donc, fonction virtuelle pure => plus de NVI.

    Citation Envoyé par Jean-Marc.Bourguet Voir le message
    Ca me semble independant. Et je vois bien clone utilisé par un certain type d'abstract factory pour son fonctionnement.
    Tu as certainement raison, mais je ne parviens pas à trouver un exemple.

    Après, j'ai tout de même trouvé un cas où le clone() est fort utile: http://en.wikipedia.org/wiki/Curious...y_construction. Je trouve ce pattern fort utile et je pense que je vais l'intégrer dans ma "boite à outil". Mais cela reste, selon moi, un cas particulier.

  4. #4
    Expert confirmé

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Par défaut
    Citation Envoyé par r0d Voir le message
    C'était plus une tentative d'humour qu'un argument. Passons.
    Ma reponse etait sur le meme ton; notre humour a l'air d'etre aussi convainquant que tes arguments

    Je me trompe sans doute, et c'est la raison pour laquelle j'ai ouvert cette discussion. Mais il me semble que le clone() de la classe mère va, généralement, être virtuelle pure. On ne peut pas cloner un objet si on ne sais pas exactement ce qu'il contient; on ne peut pas faire de "clonage partiel", ou alors ce ne serait plus un clone. Et donc, fonction virtuelle pure => plus de NVI.
    C'est quoi NVI pour toi? (le pattern habituel Base* clone() { return do_clone(); } me semble applicable).

    Tu as certainement raison, mais je ne parviens pas à trouver un exemple.
    Dans une approche pour fonctionner comme un systeme transactionnel, on peut faire un clone de ce sur quoi on va travailler et a la fin ou committer le clone apres modification (et delete de l'original) ou conserver l'original (et delete du clone). C'est un point de compromis different qu'un systeme base sur des vues.

  5. #5
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Jean-Marc.Bourguet Voir le message
    C'est quoi NVI pour toi? (le pattern habituel Base* clone() { return do_clone(); } me semble applicable).
    Ok. En fait j'avais mal compris le pattern clone(). Comme quoi, j'ai bien fait d'ouvrir cette discussion
    Je retourne à mes livres
    Merci pour ton intervention.

  6. #6
    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,

    Ce qui est vrai, c'est que tu dois choisir entre le retour co variant (qui te permettrait de récupérer un pointeur sur un objet de type dérivé) et le NVI, mais, de manière générale, tu es de toutes manières obligé de choisir entre les deux

    De plus, si tu considères que les classes ayant sémantique d'entité sont non copiables (car elles devraient l'être), c'est peut etre l'une des seules possibilité qui te sera donnée si tu dois manipuler des pointeurs intelligents (unique_ptr en premier) dans un pattern composite.

    Imaginons que, dans ton pattern composite, tu doive pouvoir gérer un grand nombre de types distincts (par exemple, un widget de n'importe quel type concret qui peut avoir comme enfant un nombre quelconque de widget de n'importe quel type concret).

    Pour que les enfants soient détruits automatiquement, et dans une optique RAII, tu utiliseras sans doute un (conteneur de) std::unique_ptr pour contenir les enfants, ce qui correspondrait à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Widget
    {
        private:
            std::vector<std::unique_ptr<Widget>> children_;
    };
    Tu pourrait, bien sur, ajouter une fonction addChild qui prendrait un pointeur nu sur Widget et laisser la fonction s'occuper de gérer l'insertion du unique_ptr, mais, à mon sens, cela aurait deux défauts majeurs:

    Le premier serait peut etre de laisser croire à l'utilisateur qu'il "garde le controle" de la durée de vie de l'objet pointé, car sa logique pourrait se baser sur un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Widget * ptr = new DerivedWidget(/* paramètres */);
    /* holder est de type widget ;) */
    holder.addWidget(ptr);
    /*...*/
     
    //Mince, il ne faut pas que j'oublie de détruire mon pointeur
    delete ptr;
    sauf que, dans le cas présent, ce n'est plus à l'utilisateur de s'occuper de la libération de la mémoire, vu qu'elle sera prise en charge par le unique_ptr

    Le deuxième défaut est le phénomène inverse : L'utilisateur pourrait, à tord, penser que la fonction addWidget crée de toutes manières un clone / une copie du pointeur passé, car il garde en tete qu'il sera géré par un unique_ptr et écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    DerivedWidget temp(/*...*/ );
    holder.addChild(&temp);
    avec les conséquences désastreuse que cela ne manquera pas d'entrainer, car le pointeur sera invalidé dés la sortie de la portée et que, si on arrive jusque là, appeler delete sur une adresse mémoire qui n'a pas été allouée dynamiquement occasionne des catastrophes

    Si, par contre, la fonction addChild prend une référence constante sur un widget (et plus encore si elle renvoie une référence non constante sur l'enfant effectivement inséré), tu retires de l'esprit de l'utilisateur tout doute quant au fait qu'il doit ou non utiliser l'allocation dynamique:

    Personnellement, si je suis en présence d'une fonction prenant une référence constante sur un objet, ou bien j'utilise une des variables que j'ai à ma disposition, ou bien j'utilise une variable temporaire anonyme.

    Mais, en tout état de cause, je ne vais pas créer spécialement un objet par allocation dynamique pour transmettre "ce qui est pointé par le pointeur" que je viens de déclarer à la fonction

    Le problème est, bien sur, que pour passer d'une référence constante à un pointeur sur objet alloué dynamiquement nécessaire à unique_ptr, ben, il faut pouvoir copier le dit objet

    Et comme l'objet est, par défaut non copiable, il ne reste plus qu'une solution : le clonage

    Au final, je verrais bien ma classe Widget sous une forme proche de
    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
    class Widget
    {
        public:
        /* je ne m'intéresse ici qu'à l'interface de gestion des enfants et au clonage ;) */
        Widget & addChild(Widget const & toadd)
        {
            children_.push_back(std::move(std::unique_ptr<Widget>(toadd.clone()));
            return child(childCount()-1);
        }
        size_t childCount() const
        {
            return children_.size();
        }
        Widget &  child(size_t index)
        {
            return const_cast<Widget&>(
                static_cast<const Widget*>(this)->child(index));
        }
        Widget const & child(size_t index) const
        {
            if(index >= childCount())
                throw IndexOutOfBoundException();
            return *(children_[index].get());
        }
        void removeChild(size_t index)
        {
            if(index >= childCount())
                throw IndexOutOfBoundException();
            children_.erase (children_.begin()+index);
        }
        Widget * clone() const;
        {
            Widget *temp = doClone();
            for(size_t i = 0; i<childCount();++i)
                temp->addWidget(child(i));
            return temp;
        }
        private:
            std::vector<std::unique_ptr<Widget>> children_;
            virtual Widet * doClone() const = 0;
    };
    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

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

Discussions similaires

  1. Les globales c'est le mal .. oui mais pourquoi?
    Par sloshy dans le forum Débuter
    Réponses: 4
    Dernier message: 26/02/2009, 15h45
  2. ma ConnectionString est surement mal configuré
    Par getule dans le forum ASP.NET
    Réponses: 10
    Dernier message: 12/11/2008, 20h39
  3. l'auto_increment est-il mal?
    Par grabriel dans le forum Langage SQL
    Réponses: 10
    Dernier message: 02/07/2008, 09h01

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