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 :

opérateur d'affectation: copie implicite ou explicite [Débat]


Sujet :

C++

  1. #41
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tu commence à approcher du problème, mais tu n'as apparemment toujours pas de vue d'ensemble.
    Pour cette fois, je pense que ma vision d'ensemble du problème et totalement correct. D'ailleurs le bench que j'ai fait (où l'ensemble des codes ne présentent pas de problème de fuite mémoire il me semble), montre bien qu'il y a bien un comportement d'elision, même avec de l'allocation dynamique.

    Citation Envoyé par koala01 Voir le message
    Pour rappel, il y a la règle des trois grands, qui dit que tu dois définir l'opérateur d'affectation, le constructeur par copie et le destructeur si tu donnes une définition pour ne serait-ce qu'une des fonctions en question.
    Je crois avoir respecter cette règle partout.

    Citation Envoyé par koala01 Voir le message
    Pour rappel, il y a la règle des trois grands, qui dit Nous partons donc sur une base 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
    struct A{
        A(int taille = 0): taille(taille), ptr(taille==0?  0 : new int[taille]){} 
        A(A const & a): taille(a.taille), ptr(a.taille==0?  0 : new int[a.taille])
        {/* COPIE de ce qui est pointé par a.ptr */;} // !!!!
        A & operator = (A const & a){ 
            A temp(a); //1
            swap(ptr, temp.ptr); 
            taille = a.taille; // c'est pas le plus important ;)
            return *this;
        }
        /* OU   OU    OU */
        A & operator = (A  temp){ // comme ca, je garde le même nom ;)
            //1
            swap(ptr, temp.ptr);
            taille = temp.taille; // c'est pas le plus important ;)
            return *this;
        }
        int taille;
        int ptr *;
    };
    Tu es d'accord avec moi que, pour qu'un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(){
    A a(15);
    /* ... */
        A copy(a);
       assert(a.ptr[3] == copy.ptr[3]);
       copy.ptr[3]= 125; //en fait, tout autre valeur que la valeur de a.ptr[3]
       assert(a.ptr[3]!= copy.ptr[3]);
       return 0;
    }
    soit valide, on est bel et bien obligé d'avoir un constructeur par copie (non trivial) dans le genre de celui que je présente, oui
    Jusque là, oui.

    Citation Envoyé par koala01 Voir le message
    Et tu seras d'accord pour estimer que si l'on veut écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main(){
    A a(15);
    A assign(20);
    /* ... */
        A assing = a;
       assert(a.ptr[3] == assing .ptr[3]);
       assing .ptr[3]= 125; //en fait, tout autre valeur que la valeur de a.ptr[3]
       assert(a.ptr[3]!= assing .ptr[3]);
       return 0;
    }
    il faut impérativement, dans l'opérateur d'affectation:
    1. que l'on dispose d'un pointeur sur un bloc mémoire correctement alloué de taille suffisante pour représenter les 15 éléments contenus à l'adresse correspondant à a.ptr
    2. que ce bloc mémoire doit est différent de a.ptr
    3. que les 15 éléments de a.ptr aient été correctement copiés dans le bloc mémoire
    4. que l'on ait veillé, d'une manière ou d'une autre,
      • à libérer la mémoire allouée à assing.ptr
      • à assigner à assing.ptr l'adresse du bloc mémoire dans lequel on a copié les éléments
    Finalement, le seul point qui ne posera aucun problème, c'est le membre taille, parce qu'on peut tout aussi bien le copier que l'assigner sans que cela fasse la moindre différence.
    Pas de problème jusque là, si ce n'est que tu te places dans un cas où il ne peut y avoir élision, cas où les deux version de l'opérateur d'affectation ont le même comportement, ce n'est donc pas vraiment dans le sujet, AMA.

    Citation Envoyé par koala01 Voir le message
    Au pire, nous pourrions dire à son sujet qu'il est sans doute préférable de l'assigner directement plutôt que de se taper un processus de copie + swap (car meme une copie (qui n'est qu'une affectation dans le cas présent) et une affectation a de grandes chances d'être plus rapide qu'une copie et un swap du fait que le swap est l'équivalent à deux Xor)
    Oui, cependant on rendre dans la micro-optimisation là, la discussion tournait autour de l'écriture idiomatique de fonction membre spéciales, l'écriture de code spécifique pour profiter de micro-optimisation est loin d'être idiomatique, AMA.

    Citation Envoyé par koala01 Voir le message
    Ce qui importe surtout, c'est que, quelle que soit la manière dont on puisse envisager les choses, il faut forcément que l'on dispose d'un pointeur (temp.ptr dans mon exemple) qui représente un état strictement similaire, en nombre d'éléments et en valeurs des différents éléments à celui de l'objet d'origine.
    En effet.

    Citation Envoyé par koala01 Voir le message
    Comme je l'ai dit plus tôt, il y a effectivement moyen de ne pas passer par la copie complète de l'objet, en travaillant avec un pointeur temporaire, mais, à partir du moment où l'on part du principe que l'on fait une copie, qu'elle soit implicite (transmission par valeur) ou explicite (transmission par référence constante + copie), il faut que cette copie existe et soit cohérente.
    Oui, je n'ai jamais dit le contraire, l'élision permet d'éviter la copie dans le cas d'un temporaire, ce qu'on récupère est bien totalement cohérant, et l'on peut le manipuler commeon veut, son caractère de temporaire fait qu'il va être détruit.

    Citation Envoyé par koala01 Voir le message
    Il y a peut etre moyen d'éviter une copie avant d'arriver à l'opérateur d'affectation, il y a peut etre moyen d'en éviter une après l'opérateur d'affectation, mais, si l'on veut que l'opérateur d'affectation (et encore, j'ai des doutes du fait que la copie devient un prérequis au comportement adéquat de l'opérateur d'affectation, mais comme je n'ai que mon intuition sur ce point... )
    • évite les fuites mémoire (en s'assurant que la temp.ptr prend l'adresse "de base" de assing, dans mon exemple)
    • assigne bel et bien à assing.ptr un état en tout point conforme à celui que l'on observe auprès de a.ptr
    On a beau se dire que temp est, quoi qu'il arrive un objet temporaire, temp n'est ni un objet construit par défaut, ni un objet seulement "partiellement construit", mais bel et bien une copie conforme, pleine et entière de l'objet qui aura servi de paramètre.
    Trouves moi des fuites mémoire dans les codes de mon bench, je n'ai pas vérifier avec Valgrind, mais je suis quasi-certain qu'il n'y en a pas (je n'accorde en général aucun crédit à la vérification de fuite via le gestionnaire des tâche windows, cependant dans ce cas, l'expérience est repeté pas loin d'un demi milliard de fois, j'aurais une fuite mémoire d'un int, je crois qu'elle se verrait même au gestionnaire des tâches).

    Citation Envoyé par koala01 Voir le message
    S'il n'en allait pas ainsi, nous ne pourrions en aucun cas prévoir où se retrouverait temp.ptr, mais surement pas dans l'éther, et de préférence à une adresse mémoire à laquelle il serait particulièrement malencontreux d'essayer d'accéder en écriture (ou d'essayer d'invoquer delete dessus)
    Tout ton raisonnement est correct, mais tu ne te places jamais dans le cas où il peut y avoir élision, ainsi en effet aucune copies ne peut être élider ...

    Pour rappel le cas d'élision pour l'opérateur d'affectation est :
    Les autres sont identique que ce soit avec la version A ou const A& de l'opérateur d'affectation.

  2. #42
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 398
    Par défaut
    Je plussoie, dans cette situation (construction d'un temporaire et passage immédiat à operator= la copie peut être élidée.

    Toutefois en C++11, on a mieux pour gérer les temporaires: Les move semantics, qui seront toujours plus rapides que le copy-and-swap. Si les deux versions operator=(A) et operator=(A &&) sont implémentées, c'est la seconde qui sera utilisée ici.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #43
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    La variable qui est bindé dans le paramètre operator = n'a pas de nom dans le programme (dans le cas d'une copy ellision) aucune chance dés lors que l'on puisse y faire référence par la suite.

    Dans l'operateur d'affectation, via un swap on s'assure que le destructeur du paramètre (il sera appelé quand la fonction operator = sera terminé) deletera bien la zone mémoire initialement pointé par le ou les membres de l'objet sur lequel l'operateur = est appelé. Pas de problème donc de double delete ou de zone mémoire partagé par deux objets.


    ===> aucune copie n'est donc requise

  4. #44
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    @Médinoc: Je suis d'accord, mais Koala a exclue le C++11 et la move-semantic de la discussion dès le début. De plus dans le cas de donnée membre allouées dynamiquement, tu devras surment utiliser un swap même en utilisant la move-semantic, ce qui donnera les même performance. A la différence que la move-semantic n'a pas besoin de supposer qu'il y a élision pour avoir ces dites performance.

    Et dans le cas où T&& et T sont présents, il y a ambiguité (déjà rappelé au début de la conversion par gbdivers).

    @guillaume07: Je suis d'accord avec toi, cependant la destruction à lieu au moment de la destruction du temporaire et non celle du pramètre :
    Norme, 12.8.31 :
    In such cases, the implementation treats the source and target of the omitted copy/move
    operation as simply two different ways of referring to the same object, and the destruction of that object
    occurs at the later of the times when the two objects would have been destroyed without the optimization.

  5. #45
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Je plussoie, dans cette situation (construction d'un temporaire et passage immédiat à operator= la copie peut être élidée.

    Toutefois en C++11, on a mieux pour gérer les temporaires: Les move semantics, qui seront toujours plus rapides que le copy-and-swap. Si les deux versions operator=(A) et operator=(A &&) sont implémentées, c'est la seconde qui sera utilisée ici.
    encore une fois grâce à la copy ellision le copy-and-swap idiom se retrouve à ne faire qu'un swap, la move semantics n'apporte que la garantie qu'il n'y ait pas de copie si une rvalue est passé dans le cas ou le compilateur ne sait pas faire de copy ellision (sous réserve que le type passé est un constructeur A(A&&))

  6. #46
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 398
    Par défaut
    @FLob09: OK, je n'avais pas bien vu.

    Juste une remarque:
    De plus dans le cas de donnée membre allouées dynamiquement, tu devras surment utiliser un swap même en utilisant la move-semantic, ce qui donnera les même performance.
    { this->ptr = src.ptr; src.ptr=NULL; } comprend une affectation de moins qu'un swap.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #47
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    @Médinoc: T'as une fuite mémoire là, les données qui étaient dans this->ptr ne sont pas libérées.

    @guillaume07: Si tu fais un operator=(const T&) et un operator=(T&&), tu n'as pas techniquement besoin du move-ctor. Cependant il est probablement que tu devras quand même le faire (rules of three/four/five).

    PS: J'ai donné (plus tôt dans la conversation) des formes idiomatiques de ces opérateurs dans le cas du C++11, http://www.developpez.net/forums/d13...e/#post7208526 elles ne sont pas forcément parfaites (j'ai pu oublier un détail), mais elles me semble correctes dans l'idée.

  8. #48
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    @guillaume07: Si tu fais un operator=(const T&) et un operator=(T&&), tu n'as pas techniquement besoin du move-ctor. Cependant il est probablement que tu devras quand même le faire (rules of three/four/five).
    je m'étais arrêté sur un unique operator=(T).

    en faite je ne vois pas l'intérêt de fournir un operator= différent pour les lvalue et rvalue versus un unique operator(T)

  9. #49
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 398
    Par défaut
    Ah oui, j'ai confondu constructeur et opérateur d'affectation. Je n'ai rien dit.
    PS: Bonne nuit.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  10. #50
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Citation Envoyé par guillaume07 Voir le message
    je m'étais arrêté sur un unique operator=(T).

    en faite je ne vois pas l'intérêt de fournir un operator= différent pour les lvalue et rvalue versus un unique operator(T)
    Les performances que tu obtiens avec la copy elision supposent quand même que cette copy elision a bien lieu, ce qui n'est pas nécessairement le cas et est difficile à déterminer. Alors qu'avec les deux versions, tu obtiens ces mêmes performances de manière systèmatique.

    Mais sinon ce je suis d'accord (cf le message que j'ai mis en lien dans le message précédent).

  11. #51
    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 Flob90 Voir le message
    Tout ton raisonnement est correct, mais tu ne te places jamais dans le cas où il peut y avoir élision, ainsi en effet aucune copies ne peut être élider ...
    Mais tu remarqueras que c'est le cas que j'ai mis en avant depuis le tout début de cette discussion, et bien avant que l'on en arrive à considérer l'élision de copie


    Citation Envoyé par Médinoc Voir le message
    Je plussoie, dans cette situation (construction d'un temporaire et passage immédiat à operator= la copie peut être élidée.
    Merci, ca me rassure
    Toutefois en C++11, on a mieux pour gérer les temporaires: Les move semantics, qui seront toujours plus rapides que le copy-and-swap. Si les deux versions operator=(A) et operator=(A &&) sont implémentées, c'est la seconde qui sera utilisée ici.
    on est bien d'accord avec le fait que la sémantique de mouvement peut etre plus rapide qu'une swap, mais j'ai écarté cette possibilité depuis le début

    Citation Envoyé par guillaume07 Voir le message
    La variable qui est bindé dans le paramètre operator = n'a pas de nom dans le programme (dans le cas d'une copy ellision) aucune chance dés lors que l'on puisse y faire référence par la suite.

    Dans l'operateur d'affectation, via un swap on s'assure que le destructeur du paramètre (il sera appelé quand la fonction operator = sera terminé) deletera bien la zone mémoire initialement pointé par le ou les membres de l'objet sur lequel l'operateur = est appelé. Pas de problème donc de double delete ou de zone mémoire partagé par deux objets.


    ===> aucune copie n'est donc requise
    non, parce que pour que le pointeur qui se trouve à gauche de l'opérateur doit obtenir une adresse valide et dont le contenu représente en tout point les valeurs de l'objet qui se trouve à droite.

    Pour que cela puisse se faire, il faut bien que, avant le swap, ton objet temporaire présente ces deux caractéristiques!!!

    Tu ne peux pas, avant le swap, avoir dans ton objet temporaire un pointeur qui pointe "vers l'éther" ou qui est dans les choux, vu que c'est l'adresse de ce pointeur qui sera fournie au pointeur de destination!

    Il faut donc qu'il y ait au minimum eu allocation dynamique de la mémoire et copie des éléments de l'objet d'origine (autrement dit : copie d'une bonne partie de l'objet).

    Tu ne peux avoir élision de la copie que si la copie renvoyée est strictement identique à la copie créée, le fusse-t-elle de manière temporaire.

    Si, par nécessité (ici, le fait de s'assurer que delete sera bel et bien appelé sur un pointeur qui était valide mais que le pointeur en question obtient une nouvelle adresse mémoire valide), le fait de copier ton objet implique qu'il faille modifier la copie avant de la renvoyer (ici, elle est modifiée avant le swap), le compilateur n'a pas d'autre choix que de laisser la copie s'effectuer en totalité afin que le swap puisse s'effectuer sur un objet valide, et ce, même si c'est pour qu'il soit détruit dans les micro secondes qui suivent.

    C'est ce qui me fait dire que, au final, la copie ne pourra sans doute que très rarement être éludée, car, si ta classe dispose d'un membre qui dispose lui-même d'un membre qui ... (on pourrait continuer longtemps comme cela ) ne présente pas la particularité d'être trivialement copiable / assignable, tu risques de te retrouver dans l'obligation de recopier la quasi totalité des membres de manière systématique.

    Autrement dit, dés que tu as une std::string ou n'importe quelle collection de la STL, comme membre, tu peux dire adieux à l'élision de la copie
    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. #52
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Les performances que tu obtiens avec la copy elision supposent quand même que cette copy elision a bien lieu, ce qui n'est pas nécessairement le cas et est difficile à déterminer. Alors qu'avec les deux versions, tu obtiens ces mêmes performances de manière systèmatique.

    Mais sinon ce je suis d'accord (cf le message que j'ai mis en lien dans le message précédent).
    d'après ma comprehension des choses, si la copy elision n'a pas lieu pour une raison ou une autre, tant que le type passé en paramètre implémente un move-ctor ça revient exactement au même que si operator=(A&&) est appelé si ça n'est pas le cas je suis preneur d'une explication

  13. #53
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par koala01 Voir le message

    Tu ne peux pas, avant le swap, avoir dans ton objet temporaire un pointeur qui pointe "vers l'éther" ou qui est dans les choux, vu que c'est l'adresse de ce pointeur qui sera fournie au pointeur de destination!
    Franchement je ne sais pas où tu fais un blocage, mais définitivement l'objet temporaire pointe bel et bien sur une adresse valide et non "vers l'éther"

  14. #54
    Membre Expert

    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 : 35
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Personnelement, et je pense qu'on est plusieurs dans ce cas, le débat se résume à, qu'est ce qui est mieux en C++03 entre :
    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
     
    T& T::operator=(const T& t)
    {
      T u(t);
      using std::swap;
      swap(*this,u);
      return *this;
    }
    //OU
    T& T::operator=(T t)
    {
      using std::swap;
      swap(*this,t);
      return *this;
    }
    Et à cette question la réponse est, sans aucune hésitation, la seconde solution car elle permet de profiter de la copie élision dans certain cas, ce qui augmente considérablement les perfomances (on gagne 50% de perf d'après le bench que j'ai fais ... c'est pas négligeable, AMA).

    Après que ce soit un opérateur ou autre chose, qu'on fasse quelque chose ou rien de spécial avec la copie, ne change rien.

    @guillaume07: Ca ne revient pas au même. Dans un cas tu as un constructeur qui est appelé (operator=(T) avec T(T&&)) alors que dans le second cas (operator=(T&&)) non.

  15. #55
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    @guillaume07: Ca ne revient pas au même. Dans un cas tu as un constructeur qui est appelé (operator=(T) avec T(T&&)) alors que dans le second cas (operator=(T&&)) non.
    hum ok je pensais que même avec operator=(T&&) T(T&&) serait appelé,mais ça n'est effectivement pas le cas, optimalement donc on se doit de fournir deux implémentations de l'operateur = si je te suis bien .. operator=(const T&) et operator=(T&&) ?

  16. #56
    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 guillaume07 Voir le message
    Franchement je ne sais pas où tu fais un blocage, mais définitivement l'objet temporaire pointe bel et bien sur une adresse valide et non "vers l'éther"
    Et comment veux tu qu'il pointe vers autre chose que l'éther s'il n'est pas, au minimum, basé sur un objet valide

    Tu pourrais avoir une rvalue référence (et donc avoir une sémantique de mouvement si, en reprenant l'exemple précédant, tu avais une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    int main(){
        foo(A(10)); // foo prend un objet temporaire non nommé
    }
    Là, oui, foo pourrait soit accepter une référence constante (*), soit une rvalue reference(**) et donc supporter la sémantique de mouvement.

    (*) Cas d'origine : on peut accepter une référence constante sur un objet temporaire non nommé parce que cela ne sert "à rien" de permettre la modification d'un objet auquel nous n'aurions de toutes façons plus accès une fois sorti de la fonction (et qui sera d'ailleurs détruit lorsqu'on quitte la fonction)

    (**) nouveauté C++11: on accepte la création d'un objet temporaire et on accepte l'idée qu'il puisse, le cas échéant, etre modifié de manière particulière (***)

    (***)on peut éventuellement s'éviter tout le travail de création d'un objet similaire (qui devrait se faire par copie!!!) en décidant de transférer, tout simplement, son contenu à l'objet en question, mais:
    • ce n'est vraiment efficace... que lorsqu'il y a des pointeurs vers des adresses mémoire allouées dynamiquement
    • cela a pour résultat d'invalider complètement l'objet temporaire
    • toute tentative d'accès à l'objet temporaire après avoir obtenu une sémantique de mouvement résulte en un comportement indéfini
    C'est pour cette raison que l'objet se doit d'être temporaire!!!

    Mais il a beau être temporaire, et on a beau pouvoir "prendre certaines libertés" (comme le vider intégralement de sa substance ) par rapport à cet objet temporaire, il n'empêche que cet objet se doit d'être correctement construit à la base!

    Si l'objet temporaire doit représenter une copie d'un objet existant, il faut, d'abord et avant tout... qu'il y ait copie du dit objet, et que cette copie puisse être considérée comme temporaire.

    L'élision de la copie (et l'utilisation de la sémantique de mouvement) ne pourra se faire que lorsque l'on tentera... de copier cet objet temporaire ou d'affecter afin d'obtenir un objet (potentiellement non temporaire celui-là) final
    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

  17. #57
    gl
    gl est déconnecté
    Rédacteur

    Homme Profil pro
    Inscrit en
    Juin 2002
    Messages
    2 165
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Juin 2002
    Messages : 2 165
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Et comment veux tu qu'il pointe vers autre chose que l'éther s'il n'est pas, au minimum, basé sur un objet valide
    Mais il est basé sur un objet valide : le paramètre de l'opérateur.

    L'élision de copie ne signifie pas fournir une zone mémoire non initialisé mais remplacer, dans les cas où s'est possible (typiquement l'opérande ne sera plus utilisé par la suite), une séquence "construction d'un objet + copie vers le paramètre" par une "construction directe de l'objet dans le paramètre" (et pas de destruction de l'objet temporaire, seulement du paramètre) : on gagne bel et bien une copie sans pour autant introduire de fuite mémoire, de double libération ou autre. Et bien que ce n'est pas toujours applicable, la complexité de la copie ne me semble par intervenir dans la décision d'éluder ou non la copie.

    Alors que dans le cas de passage par référence constante + création explicite d'une copie dans le corps de l'opérateur, on ne sait pas gagner cette copie qui a toujours lieu même si l'opérande n'est plus utilisé par la suite.


    Et le résultat est que dans quelques cas d'utilisation de ton opérateur (probablement minoritaires, certes) on gagner et que dans tous les autres on ne perd rien.

  18. #58
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 398
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 398
    Par défaut
    En, effet. Koala01, j'ai l'impression que tu confonds "passage par valeur" et "passage par copie systématique dans tous les cas".

    Quand on fait foo(1, A(10), 42) avec void foo(int, A, int), la copie sera élidée en construisant directement l'objet à sa destination (c'est-à-dire, entre les deux paramètres entiers).
    La raison pour laquelle c'est acceptée est qu'il n'y a aucun moyen de référencer l'objet après l'appel de la fonction; l'appelant ne peut donc pas voir que l'objet lui-même a été modifié plutôt qu'une copie de l'objet.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  19. #59
    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
    Je vais précisier un peu d'avantage, parce que je vois où est le problème de l'incompréhension.

    On ne peut envisager d'éluder la copie que sur un objet temporaire, c'est à dire un objet qui a été créé "juste pour l'occasion" ( par exemple en appelante foo(A(10)) ).

    A ce moment là, oui, il y aura élision de la copie.

    Par contre, s'il s'agit d'un objet non temporaire, et a fortiori s'il a été modifié (mais comment le savoir ), tu ne pourras jamais éluder la copie d'un objet qui contient un pointeur.

    Avec un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int main(){
        A a(10);
        foo(a);
    }
    tu ne pourrais pas avoir d'élision de la copie, parce qu'autrement, le seul pointeur valide que tu aurais serait... celui de a, que tu ne peux pas te permettre de déplacer vers une autre instance de A.

    Soit tu transmet a par valeur, et il y a copie implicite d'office, soit tu transmet a par référence (ou par pointeur), et tu dois faire la copie explicitement.

    Et, dans cet exemple (qui n'est jamais que celui que j'ai utilisé tout au long de la discussion ), ne s'appliquera de toutes façons pas, parce que tu n'as pas une référence sur un objet temporaire.

    La seule chose sur laquelle le compilateur puisse se rabattre, ce sont le constructeur par copie de A et son opérateur d'affectation, et il n'y a pas d'élision de copie possible, parce que ces deux fonctions principales sont non triviales (en présentant les choses de la sorte, on en arrive même à se dire que, le fait qu'un constructeur par copie soit trivial ou non ne changera rien: la copie ne pourra être éludée qu'avec des objets temporaires )
    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

  20. #60
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Je vais précisier un peu d'avantage, parce que je vois où est le problème de l'incompréhension.

    On ne peut envisager d'éluder la copie que sur un objet temporaire, c'est à dire un objet qui a été créé "juste pour l'occasion" ( par exemple en appelante foo(A(10)) ).

    A ce moment là, oui, il y aura élision de la copie.

    Par contre, s'il s'agit d'un objet non temporaire, et a fortiori s'il a été modifié (mais comment le savoir ), tu ne pourras jamais éluder la copie d'un objet qui contient un pointeur.
    si tu remontes le thread tu verras qu'il a été plusieurs fois précisé (dont au moins une fois par moi) que l'éllision de copie ne pourra avoir lieu uniquement si operator = est appelé avec une rvalue. Et c'est précisement pour optimiser ces cas là qu'il est préférable d'écrire l'opérateur d'assignement avec un paramètre par valeur et non pas comme une référence constante.


    Citation Envoyé par guillaume07 Voir le message
    tu peux avoir la certitude que la copy ellision aura lieu .... tout simplement si ton operator =(T t) est appelé avec une rvalue
    réponse à laquelle tu as répondu :

    Citation Envoyé par koala01 Voir le message
    La seule certitude que tu as en déclarant operator=(T t), c'est que la copie sera bel et bien effectuée, l'argument étant passé par valeur.

    Cela ne signifie absolument pas qu'il y ait élision de la copie, bien au contraire, ca signifie que tu force le compilateur à faire une copie, vu qu'il ne pourra pas l'élider pour cause de modification

    Peut-être ne faisais-tu pas le rapprochement entre variable temporaire et rvalue ? une variable temporaire est une rvalue.

Discussions similaires

  1. Réponses: 7
    Dernier message: 17/08/2014, 15h20
  2. Constructeur de copie, et opérateur d'affectation.
    Par Invité dans le forum Débuter
    Réponses: 49
    Dernier message: 03/04/2010, 13h13
  3. Réponses: 12
    Dernier message: 12/07/2007, 14h17
  4. Classe Interface et Opérateur d'affectation!
    Par Rodrigue dans le forum C++
    Réponses: 6
    Dernier message: 07/02/2007, 14h45
  5. [langage] opérateur d'affectation binaires
    Par biglebowski13 dans le forum Langage
    Réponses: 6
    Dernier message: 21/11/2006, 09h51

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