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. #21
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par guillaume07 Voir le message
    si tu as définit un operator = et que tu souhaites que ta classe supporte la "sémantique de move" alors c'est que tu as définis un constructeur de copie prenant une rvalue reference...donc si il n'y pas d'invalidation de l'élément transmis c'est que tu as délibérement fait ce choix lors de ton implémentation du constructeur de copie prenant une rvalue reference
    Mais ici, il n'est pas question de sémantique de mouvement, il est question d'assurer une copie et une affectation qui évite les problèmes majeurs liés à la gestion manuelle de ressources allouées de manière dynamique
    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

  2. #22
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    oui d'où la définition de l'operateur d'assigment comme ça
    plutôt que comme ça
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    T& operartor=(const T& t)
    dû à l'optimisation du passage par valeur i.e: copy elision

  3. #23
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Mais il est impossible d'avoir une élision de copie, quoi qu'il advienne!!!

    Tu ne peux avoir une élision de copie qu'à partir du moment où tu as la certitude que la copie reste inchangée entre le moment où elle est créée et le moment où elle est renvoyée.

    si tu dois, pour les besoins impérieux de ta fonction, modifier la copie, que ce soit en swappant certaines données ou simplement en les modifiant avant de renvoyer la dite copie, il n'y a, à mon sens du moins (et depuis le temps que je l'affirme, je présumes que quelqu'un se serait fait un plaisir de me contredire si je me trompais sur ce coup là ) aucune optimisation possible en terme d'élision de copie!!!

    Si tu décides de ne pas créer une copie qui est destinée à être modifiée, dis moi à quoi tu compte appliquer les changements en question, c'est aussi simple que ca
    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

  4. #24
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par Joel F Voir le message
    Le vrai interet de copier l'argument implicitement est que ca renforce l'exception level de =. En effet si la copie de l'argument echoue a cause d'une exception, la cible reste inchangé, rendant la transaction roll-backable.
    J'avais pas vu ça, je comprend pas trop non plus, si la copie échoue (qu'elle soit implicite ou explicite), une exception est lancée avant le swap, donc la cible reste inchangée

  5. #25
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Mais il est impossible d'avoir une élision de copie, quoi qu'il advienne!!!

    Tu ne peux avoir une élision de copie qu'à partir du moment où tu as la certitude que la copie reste inchangée entre le moment où elle est créée et le moment où elle est renvoyée.

    si tu dois, pour les besoins impérieux de ta fonction, modifier la copie, que ce soit en swappant certaines données ou simplement en les modifiant avant de renvoyer la dite copie, il n'y a, à mon sens du moins (et depuis le temps que je l'affirme, je présumes que quelqu'un se serait fait un plaisir de me contredire si je me trompais sur ce coup là ) aucune optimisation possible en terme d'élision de copie!!!

    Si tu décides de ne pas créer une copie qui est destinée à être modifiée, dis moi à quoi tu compte appliquer les changements en question, c'est aussi simple que ca
    tu peux avoir la certitude que la copy ellision aura lieu .... tout simplement si ton operator =(T t) est appelé avec une rvalue

  6. #26
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    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
    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
    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

  7. #27
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    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
    justement non!
    prends par exemple le cas suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    T func(){ return T(); }
    T f;
    f = func();
    le retour de func sera directemnt injecté en lieu et place du paramètre de l'operateur d'assignement et aucun copie n'aura lieu (dans le cas de la signature suivante T& operator =(T t))

  8. #28
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par guillaume07 Voir le message
    justement non!
    prends par exemple le cas suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    T func(){ return T(); }
    T f;
    f = func();
    le retour de func sera directemnt injecté en lieu et place du paramètre de l'operateur d'assignement et aucun copie n'aura lieu (dans le cas de la signature suivante T& operator =(T t))
    Oui, parce que T n'est pas modifié entre le moment où il est créé et le moment où il est renvoyé.

    On pourrais donc tout à fait avoir élision de la copie avec un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    T func(){
    T temp;
    /* !!! AUCUNE MODIFICATION DE TEMP ICI !!! */
    return temp;
    }
    T f;
    f = func();
    et ce, même s'il y a effectivement du code entre la création de temp et son renvoi (pour autant qu'il n'y ait pas modification (j'en doute un peu, mais, après tout, pourquoi pas ).

    Je serais tout aussi d'accord pour admettre la possibilité d'une élision de copie dans le cadre de la structure Point que j'ai présentée dans mes deux ou trois interventions précédentes, dans le sens où l'opérateur d'affectation n'aurait aucun besoin de modifier la copie de l'objet qu'il obtient.

    Mais, dans le cadre particulier d'une classe devant gérer des ressources allouées dynamiquement, tu n'as aucun moyen (*) d'empêcher la création d'une "copie de travail" d'éviter l'ensemble des écueils relatifs à la gestion manuelle de la mémoire.

    (*) enfin, qui évite autant que faire se peut d'avoir à dupliquer une partie du code du constructeur par copie et une autre partie du destructeur
    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

  9. #29
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Oui, parce que T n'est pas modifié entre le moment où il est créé et le moment où il est renvoyé.
    Non c'est parceque l'objet retourné par func() et injecté dans l'operator = est une rvalue peu importe si dans func l'objet retourné est modifié une ou cent fois

  10. #30
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Mais ca ne vaut que parce que l'opérateur d'affectation est trivial.

    Or, la règle des trois grands te dit que, à partir du moment où tu fournis une implémentation particulière pour le constructeur par copie, l'opérateur d'affectation ou le destructeur, tu es obligé de fournir une implémentation particulière pour ces trois fonctions.

    Tu ne peux donc pas avoir un opérateur d'affectation trivial dés le moment où tu as un destructeur qui fait un delete sur ne serait-ce qu'un seul de ses objets membres, parce qu'il faut éviter que la copie n'occasionne un partage des ressources, et parce qu'il faut éviter que l'affectation n'occasionne soit un partage des ressources soit une fuite mémoire.

    Et le seul moyen d'éviter la copie intégrale de l'objet serait d'avoir un opérateur d'affectation qui reprend (au moins en partie) le code du constructeur par copie et celui du destructeur, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    TableauInt & operator= (TableauInt /* const & */ rhs){
        int * temp = new int[rhs.size_]; // on profite que new lance std::bad_alloc pour s'éviter la vérification
        memcpy(temp,rhs.ptr_, rhs.size_* sizeof(int));
        /* Maintenant, on peut détruire ptr_ */
        delete [] ptr_;
        ptr_=temp;
        size_ = rhs.size_;
        return *this;
    }
    (en plus, à bien y réfléchir, tu en arrive quand même à avoir copié l'intégralité de l'objet, car size_ = rhs.size_ ne fait rien d'autre que... copier l'entier ))

    Mais à ce moment là, tu as tout intérêt à effectivement passer rhs par référence constante plutôt que par valeur, afin d'être certain qu'il n'y aura de toutes façons pas de copie du paramètre, parce qu'il serait totalement aberrant de le passer par valeur, et donc de laisser la possibilité au compilateur de créer effectivement la copie.

    C'est pour cela que je dis que, à partir du moment où tu te trouves dans une situation dans laquelle tu dois redéfinir l'opérateur d'affectation, il n'y a plus aucun moyen d'assurer une élision de copie, simplement parce que tu devras, de toutes manières, faire une copie de la totalité des membres de ton objet
    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

  11. #31
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    je suis désolé mais encore une fois non. Cela est valable quel que soit le niveau de complexité de ton opérateur d'affectation.

  12. #32
    En attente de confirmation mail

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

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    @koala01: La condition "pas de modification" c'est toi qui l'invente, jamais la norme ne mentionne une telle condition. Les conditions sont plutôt liées aux types (après avoir enlevé les qualificatifs const/volatile), et le caractère volatile de l'objet cible.

    Par contre tester la "présence" effective de cette elision n'est pas simple (ce n'est pas réelement faisable en introduisant du code : il est suceptible de changer les décisions du compilateur), je dirais même que c'est impossible à moins que le compilateur offre un outil qui te permet de les visualiser directement. Le meileur test étant encore de faire deux versions d'une même classe, avec const T& et T, et bencher dans le cas d'une elision possible (en optimisé bien entendu), si les performances sont différences, elles seront surment le signe d'une elision.

    Je ne vois pas ce qui te fait penser que cette condition de "non modification" soit nécessaire ? L'elision c'est réalisé en construisant directement l'objet dans la cible, les modifications faites entre l'original et la cible sont tout simplement directement affectés à la cible. Je suis loin de pouvoir développer un compilateur, mais ca me semble être un simple traitement de l'AST.

    Ce caractère un peu magique et non totalement déterministe (ie ca dépend du compilateur), est une des raisons pour profiter de la move-semantic quand on peut et de ne pas juste compter sur l'elision. Elle [la move-semantic] est déterministe.

  13. #33
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par koala01 Voir le message
    (et depuis le temps que je l'affirme, je présumes que quelqu'un se serait fait un plaisir de me contredire si je me trompais sur ce coup là ) aucune optimisation possible en terme d'élision de copie!!!
    Du coup ça m'a donné envie de tester

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    //g++ -std=c++11 -O3 -DCHRONO -o foo foo.cpp && ./foo 2> /dev/null
     
    #include <iostream>
    #include <ctime>
    #include <memory>
     
    #include <cstring>
     
    const int size = 5000000;
    const int nb_tests = 100;
     
    #ifdef CHRONO
    #	include <chrono>
    #	define RESET duration = 0;
    #	define START t0 = clock::now();
    #	define END duration += duration_cast<time_type>(clock::now() - t0).count();
    #else
    #	define WIN32_LEAN_AND_MEAN
    #	include <Windows.h>
    #	define RESET duration = 0.0;
    #	define START QueryPerformanceCounter(&begin);
    #	define END QueryPerformanceCounter(&end); duration += (end.QuadPart - begin.QuadPart) / freq;
    #endif // CHRONO
     
    class TableauIntRef {
     
    private:
    	int* tableau;
    	int taille;
     
    public :
    	explicit TableauIntRef(): taille(0), tableau(nullptr) { }
    	explicit TableauIntRef(int t): taille(t), tableau(new int[t]) { }
    	explicit TableauIntRef(const TableauIntRef& t): taille(t.taille), tableau(new int[t.taille]) {
    		memcpy(tableau, t.tableau, sizeof(int) * taille);
    	}
    	~TableauIntRef() { delete tableau; }
    	int getElement(int i) const { return tableau[i]; }
    	void setElement(int i, int elt) { tableau[i] = elt; }
    	int getTaille() const { return taille; }
     
    	void swap(TableauIntRef& t) {
    		std::swap(tableau, t.tableau);
    		std::swap(taille, t.taille);
    	}
     
    	TableauIntRef& operator=(const TableauIntRef& t) {
    		TableauIntRef cp(t);
    		swap(cp);
    		return *this;
    	}
     
    };
     
    class TableauIntCpy {
     
    private:
    	int* tableau;
    	int taille;
     
    public :
    	explicit TableauIntCpy(): taille(0), tableau(nullptr) { }
    	explicit TableauIntCpy(int t): taille(t), tableau(new int[t]) { }
    	TableauIntCpy(const TableauIntCpy& t): taille(t.taille), tableau(new int[t.taille]) {
    		memcpy(tableau, t.tableau, sizeof(int) * taille);
    	}
    	~TableauIntCpy() { delete tableau; }
    	int getElement(int i) const { return tableau[i]; }
    	void setElement(int i, int elt) { tableau[i] = elt; }
    	int getTaille() const { return taille; }
     
    	void swap(TableauIntCpy& t) {
    		std::swap(tableau, t.tableau);
    		std::swap(taille, t.taille);
    	}
     
    	TableauIntCpy& operator=(TableauIntCpy cp) {
    		swap(cp);
    		return *this;
    	}
     
    };
     
    template <class T>
    void useData(T& t0, T& t1) {
    	int a=0, b=0;
    	for(int i=0; i<size; ++i) {
    		t0.setElement(i, rand());
    	}
     
    	for(int i=0; i<size; ++i) {
    		a += t0.getElement(i);
    		b += t1.getElement(i);
    	}
    	std::cerr << a << b << std::endl;
    }
     
    int main(int argc, char **argv) {
     
    #ifdef CHRONO
    	typedef std::chrono::high_resolution_clock clock;
    	typedef clock::time_point time_point;
    	typedef std::chrono::microseconds time_type;
    	using std::chrono::duration_cast;
    	time_point t0;
    	long long duration;
    #else
    	LARGE_INTEGER qfreq, begin, end;
    	QueryPerformanceFrequency(&qfreq);
    	double freq = qfreq.QuadPart / (double) 1000000; // µs
    	double duration;
    #endif // CHRONO
     
    	TableauIntRef r0(size), r1;
    	TableauIntCpy c0(size), c1;
     
    	srand(time(0));
     
    	for(int i=0; i<size; ++i) {
    		r0.setElement(i, rand());
    		c0.setElement(i, rand());
    	}
     
    	RESET;
    	for(int i=0; i<nb_tests; ++i) {
    		START;
    		r1 = r0;
    		END;
     
    		// utiliser les données pour pas que le compilo vire le code
    		useData(r0, r1);
    	}
     
    	std::cout << duration / (double)nb_tests << "us (ref)" << std::endl;
     
    	RESET;
    	for(int i=0; i<nb_tests; ++i) {
    		START;
    		c1 = c0;
    		END;
     
    		// utiliser les données pour pas que le compilo vire le code
    		useData(c0, c1);
    	}
     
    	std::cout << duration / (double)nb_tests << "us (copy)" << std::endl;
     
    	return 0;	
    }
    std::chrono n'a pas l'air d'avoir la précision de QueryPerformanceCounter sous Windows, du coup j'utilise QueryPerformanceCounter si CHRONO n'est pas défini.

    g++ 4.8, binaire 32bits, sur une VM openSuse 32 bits
    9308.12 9344.98 µs (ref constante)
    8493.02 10317.2 µs (copie)

    VS2012, binaire 32 bits, sur Windows 7 64bits
    5722.98 5724.37 µs (ref constante)
    5694.41 5738.59 µs (copie)

    VS2012, binaire 64 bits, sur Windows 7 64bits
    5891.85 5988.55 µs (ref constante)
    5833.75 5923.47 µs (copie)

    On dirait bien qu'une optimisation est possible. (sous g++ du moins )

    edit:
    Satané copié / collé...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    TableauIntRef r0(size), r1;
    TableauIntRef c0(size), c1;
    Le gain vient de la mise en cache du coup, je rédit avec les bons temps bientot.

    re-édit: voila les bons temps.
    Bah c'est moins glorieux d'un coup , g++ n'a pas trop l'air d'aimer la copie explicite.

  14. #34
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    @koala01: La condition "pas de modification" c'est toi qui l'invente, jamais la norme ne mentionne une telle condition. Les conditions sont plutôt liées aux types (après avoir enlevé les qualificatifs const/volatile), et le caractère volatile de l'objet cible.

    Par contre tester la "présence" effective de cette elision n'est pas simple (ce n'est pas réelement faisable en introduisant du code : il est suceptible de changer les décisions du compilateur), je dirais même que c'est impossible à moins que le compilateur offre un outil qui te permet de les visualiser directement. Le meileur test étant encore de faire deux versions d'une même classe, avec const T& et T, et bencher dans le cas d'une elision possible (en optimisé bien entendu), si les performances sont différences, elles seront surment le signe d'une elision.
    Mais je crois qu'elle tombe sous le sens...

    Expliques moi comment tu veux envisager envisager de modifier la copie si... tu n'a pas de copie à modifier

    L'idée, c'est que l'opérateur d'affectation puisse ne pas forcément faire appel au constructeur par copie pour affecter correctement les différents membres de ta classe ou de ta structure (ou alors, c'est que j'ai vraiment mal compris le principe de l'élision de copie )

    Le problème pour que cela puisse fonctionner, c'est qu'il faut que le comportement global de l'opérateur d'affectation soit identique à celui du constructeur par copie.

    Or, selon moi (mais prouvez moi que je me trompe ) on ne peut être sur que c'est le cas que si la copie "superficielle" des membres est suffisante, à l'instar de l'implémentation que fournit le compilateur.

    Alors, oui, tant que l'opérateur d'affectation ne fait, en définitive, rien d'autre que copier les membre de l'objet qu'il reçoit en argument à la place de ses propres membres (ou, comme ce sera sans doute le cas, affecter la valeur des membres de l'objet passé en paramètre à ses propres membres), il n'y a strictement aucun problème, il y a de fortes chances que l'élision de la copie puisse effectivement se faire, à la condition toutefois que l'élision de la copie puisse, aussi, se faire pour les membres en question

    Ainsi, pour reprendre mon exemple de la tantôt, je ne doute absolument pas que l'on observera une élision de copie sur une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Point{
        public:
            Point(int x, int y):x_(x),y_(y){}
            int x() const{return x_;}
            int y() const{return y_;}
        private:
            int x_; 
            int y_;
    };
    (oui, je ne voulais pas donner l'impression que je ne l'envisageais que pour des type POD ) soit tout à fait possible.

    La raison est ici simple : x_ et y_ sont trivialement constructible, trivialement copiable et trivialement assignables.

    L'opérateur d'affectation ne doit strictement rien faire d'autre que ce que fait le constructeur par copie.

    Par contre, dés le moment où la gestion dynamique de la mémoire entre en jeu, il en va tout autrement.

    Le problème commence avec le fait qu'il faut s'assurer que la mémoire allouée à un pointeur est correctement libérée lorsque l'objet est détruit.

    On commence donc tout naturellement par implémenter un destructeur en y mettant le delete qui va bien.

    On continue fatalement en se disant que c'est très bien de veiller à libérer la mémoire, mais que si l'on écrit un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    UnType t;
    UnType copy(t);
    il faut absoluement veiller à ce que t et copy ne partagent pas les même ressources pour éviter la tentative de double libération de la mémoire.

    On implémente donc le constructeur par copie de manière à ce qu'il fasse une copie en profondeur:
    • Allocation d'une zone de mémoire suffisante pour maintenir l'ensemble des données
    • copie des données de l'objet d'origine dans l'espace nouvellement alloué
    A partir de là, je ne vois sincèrement aucun moyen d'éviter la copie, pour la simple et bonne raison que, si on ne fait pas la copie en profondeur, on se retrouvera avec un pointeur pointant sur une adresse mémoire qui risque d'être libérée à plusieurs endroits.

    On termine enfin en se disant que c'est très bien de permettre que la copie de l'objet évite de partager ses ressources avec l'objet d'origine, mais qu'il faut aussi veiller à ce que l'opérateur d'affectation occasionne la libération correcte de la mémoire allouée à l'origine, avant d'en perdre toute trace (règle des trois grands).

    Alors, bien sur, on peut s'amuser à tout faire (allocation de la mémoire pour un pointeur temporaire, copie du contenu de l'objet assigné dans la mémoire allouée au pointeur temporaire, libération du pointeur d'origine, affectation de la mémoire allouée au pointeur temporaire au pointeur d'origine et affectation des membres "classiques" un à un) comme je l'ai fait dans mon intervention précédente, mais, encore une fois, cela revient strictement à... effectuer une copie de l'objet

    Pour éviter les duplications de code (car on se retrouve au final avec une partie du code du constructeur par copie et une partie du code du destructeur dans l'opérateur d'affectation) et s'assurer que "tout est fait dans le bon ordre", on préfère utiliser l'idiome "copy and swap": on crée une copie (implicite ou explicite, là est la question à la base ), et on "swape" les différents membres.

    J'hésite d'ailleurs fortement à affirmer (car je n'en ai aucune preuve, si ce n'est mon intuition ) que la copie ne pourra être éludée que si tous les membres sont trivialement copiable (et affectable) de manière récursive.

    Ainsi, je ne crois sincèrement pas qu'il soit possible d'envisager d'éluder une copie (sauf en utilisant le principe de la "lazy copy") d'une structure proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct Adresse{
        int numro;
        std::string rue;
    };
    pour la simple et bonne raison que, bien qu'elle soit défaut constructible, copiable et affectable, std::string fournit (de manière totalement transparente pour l'utilisateur, je vous l'accorde) des comportements spécifiques de gestion dynamique de la mémoire qui doivent être pris en compte au niveau de la copie et de l'affectation

    Encore une fois, peut etre ne fais-je simplement pas assez confiance à mon compilateur sur ce coup là, et peut etre que je n'ai qu'une vision bien étriquée sur le sujet, mais dans ce cas, montrez moi le défaut dans mon raisonnement
    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

  15. #35
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Du coup ça m'a donné envie de tester

    g++ 4.8, binaire 32bits, sur une VM openSuse 32 bits
    9308.12 9344.98 µs (ref constante)
    8493.02 10317.2 µs (copie)

    VS2012, binaire 32 bits, sur Windows 7 64bits
    5722.98 5724.37 µs (ref constante)
    5694.41 5738.59 µs (copie)

    VS2012, binaire 64 bits, sur Windows 7 64bits
    5891.85 5988.55 µs (ref constante)
    5833.75 5923.47 µs (copie)

    On dirait bien qu'une optimisation est possible. (sous g++ du moins )

    edit:
    Satané copié / collé...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    TableauIntRef r0(size), r1;
    TableauIntRef c0(size), c1;
    Le gain vient de la mise en cache du coup, je rédit avec les bons temps bientot.

    re-édit: voila les bons temps.
    Bah c'est moins glorieux d'un coup , g++ n'a pas trop l'air d'aimer la copie explicite.
    Et en sauvegardant 100 fois le résultat de ce tests, tu verrais peut etre certaines valeurs s'inverser plus ou moins régulièrement

    Plus sérieusement...

    Une différence maximale d'une milli seconde (même pas tout à fait, à voir tes chiffres ) peut parfaitement s'expliquer par le simple fait que ton système d'exploitation ait décidé, à un moment donné, qu'il était temps de "donner la main" à un autre processus dont la priorité était devenue supérieure à celle de ton test

    Il n'est pas impossible que tu obtiennes des résultats en permanence très similaires en faisant tourner ce test de manière prolongée mais j'aurais personnellement tendance à mettre ces différences sur le fait que l'on ne travaille, malheureusement, pas sur des machines en temps réel (enfin, en temps constant)
    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

  16. #36
    En attente de confirmation mail

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

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    A& operator=(A a)
    {
      //1
      using std::swap;
      swap(*this,a);
      return *this;
      //2
    }
     
    //...
    A a;
    a = A(); //3
    En 3 le compilateur peut élider la copie, il construit directement l'objet en 1, cet objet sera détruit en 2. Je ne vois pas de problème.

    Le cas plus complexe c'est celui-ci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    A foo(A a)
    {
      //1
      A b; //3
      //5
      using std::swap;
      swap(a,b);
      return b;
      //2
    }
     
    A c /*4*/ = foo(A()/*5*/);
    En 5 le compilateur peut élider la copie et construire l'objet directement en 1, il sera détruit en 2. En 2 il peut élider la copie de 3 et le construire directement en 4, il sera détruit plus tard (en sorti du scope de c). Au final tu n'as aucune copie, juste la construction de ton temporaire et de ton objet. Et je ne vois pas ce que des éventuelles modification en 6 viendrait changer.

  17. #37
    En attente de confirmation mail

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

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Bench adapté de celui de Stepanov pour quantifier la pénalité d'abstraction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
     
    #include <stddef.h>
    #include <stdio.h>
    #include <time.h>
    #include <math.h>
    #include <stdlib.h>
     
    #include<utility>
     
    int iterations = INT_MAX/50;
     
    int current_test = 0;
     
    double result_times[2];
     
    void summarize() {
      printf("\ntest      absolute\n");
      printf("number    time\n\n");
      int i;
      for (i = 0; i < current_test; ++i)
        printf("%2i       %5.2fsec\n",
    	   i,
    	   result_times[i]);
    }
     
    clock_t start_time, end_time;
     
    inline void start_timer() { start_time = clock(); }
     
    inline double timer() {
      end_time = clock();
      return (end_time - start_time)/double(CLOCKS_PER_SEC);
    }
     
    template <class T>
    void test() {
      int i;
      T t;
      start_timer();
      for(i = 0; i < iterations; ++i)
          t = T();
      result_times[current_test++] = timer();
      t.foo();
    }
     
    struct A
    {
        int* i;
        A() : i(new int) {}
        A(const A& a) : i(new int(*a.i)) {}
        A& operator=(const A& a)
        { A b(a); std::swap(i,b.i);  return *this; }
        ~A() { delete i; }
        void foo(){}
    };
     
    struct B
    {
        int* i;
        B() : i(new int) {}
        B(const B& a) : i(new int(*a.i)) {}
        B& operator=(B a)
        { std::swap(i,a.i);  return *this; }
        ~B() { delete i; }
        void foo(){}
    };
     
    int main() {
      test<A>();
      test<B>();
      summarize();
    }
    VC++ en Ox Ob2 Ot
    test absolute
    number time

    0 8.79sec
    1 4.41sec
    Appuyez sur une touche pour continuer...
    GCC 4.7.1 en O2
    g++ (tdm64-1) 4.7.1
    Copyright (C) 2012 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    test absolute
    number time

    0 12.11sec
    1 5.43sec

    Appuyez sur une touche pour continuer...
    Processeur : Intel Core i3 à 2.10 GHz
    OS : Windows Seven SP1

  18. #38
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    A& operator(A a)
    {
      //1
      using std::swap;
      swap(*this,a);
      return *this;
      //2
    }
     
    //...
    A a;
    a = A(); //3
    Tant que l'ensemble des membres de A est trivialement constructible, je ne vois strictement aucune objection.

    Là où je mets en doute la capacité d'éluder la copie, c'est lorsque la copie en elle-même ne peut pas se contenter d'être superficielle.

    Nous sommes cependant d'accord que le (1) pourrait parfaitement être explicite si l'argument était passé par référence: c'est une copie obligatoire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    A& operator(A const & a)
    {
       A temp(a);//1
      using std::swap;
      swap(*this,a); // 1'
      return *this;
      //2
    }
     
    //...
    A a;
    a = A(); //3
    1' : comment pourrais tu éviter la copie en (1) (qu'elle soit implicite ou explicite, je te l'accorde), alors que tu dois t'assurer qu'un des membres qui est un pointeur pointe sur un espace mémoire suffisant pour représenter l'ensemble des données pointées par le pointeur équivalent de a et qu'elles ont été correctement copiées

    Tu ne peux pas te contenter de copier ce "qui est pointé par le pointeur" de a sur la pile, et encore moins décider de fournir l'adresse sur la pile de cette donnée à ton pointeur pour lequel tu vas appeler delete!

    En 3 le compilateur peut élider la copie, il construit directement l'objet en 1, cet objet sera détruit en 2. Je ne vois pas de problème.
    Le cas plus complexe c'est celui-ci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    A foo(A a)
    {
      //1
      A b; //3
      //5
      using std::swap;
      swap(a,b);
      return b;
      //2
    }
     
    A c /*4*/ = foo(A()/*5*/);
    En 5 le compilateur peut élider la copie et construire l'objet directement en 1, il sera détruit en 2. En 2 il peut élider la copie de 3 et le construire directement en 4, il sera détruit plus tard (en sorti du scope de c). Au final tu n'as aucune copie, juste la construction de ton temporaire et de ton objet. Et je ne vois pas ce que des éventuelles modification en 6 viendrait changer.
    Tout à fait d'accord, tant que la copie ne nécessite rien d'autre qu'une copie en surface.

    Mais il faut prendre conscience du fait que l'appel à new a un effet bien particulier qui est de te donner l'entière responsabilité de la mémoire qui a été allouée par cette commande.

    Si, pour une raison ou une autre, tu perds l'adresse de cette mémoire sans l'avoir explicitement libérée, tu obtiens une fuite mémoire que seul l'arrêt total de l'application pourra résoudre et si la fuite mémoire survient de manière trop systématique, c'est la stabilité de carrément tout le système qui risque d'être compromise à terme.

    Comme tu ne peux pas remplacer une adresse pour laquelle il faut appeler delete par une adresse de la pile tu es obligé de veiller à ce que l'adresse de remplacement soit aussi une adresse dynamiquement allouée.

    Comme, de plus, cette adresse dynamiquement allouée doit refléter un état bien particulier qui n'est pas forcément l'état qu'elle aurait lors de la création par défaut de l'objet, tu es, en plus, obligé de veiller à ce qu'elle reflète l'état actuel de l'objet copié.

    Tu ne pourrais pas envisager décemment d'intervertir les pointeurs de la copie (quelle que soit la manière dont tu l'obtiens) et de l'objet se trouvant à gauche de l'opérateur sans avoir la certitude que ces deux conditions soient remplies.

    Alors, bien sur, il reste le cas de "tous les autres membres" qui sont (peut on l'espérere) trivialement constructible et trivialement copiables.

    Mais, toute choses étant égales, que tu les copies "directement" depuis leur origine vers leur destination ou que tu utilises le constructeur par copie afin de t'assurer qu'ils présentent eux aussi un état correct par rapport à l'objet d'origine ne fera sans doute pas énormément de différence.

    La seule différence que j'envisage effectivement, c'est qu'il n'est peut etre pas obligatoire de swaper ces valeurs, mais qu'il est peut etre préférable d'assigner directement les valeurs en question
    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

  19. #39
    En attente de confirmation mail

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

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Si je suis bien, ton problème ce situe dans un cas où A ressemble à un truc comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    struct A
    {
      int* i;
      A() : i(new int) {}
      A(const A& a) : i(new int(*a.i)) {}
      ~A() { delete i; }
    };
    Je ne vois pas où est le problème. Prenons :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    A& operator=(A a)
    {
      //3
      using std::swap;
      swap(*this,a); //4
      return *this;
      //5
    }
     
    //...
    A a; //1
    a = A() /*2*/;
    //6
    Supposons que je schématise le tas. Sans élisions :
    //1
    add1 size(int) data1 //ctor new

    //2
    add1 size(int) data1
    add2 size(int) data2 //ctor new

    //3
    add1 size(int) data1
    add2 size(int) data2
    add3 size(int) data2 //copy-ctor new

    //4
    add1 size(int) data2 //swap
    add2 size(int) data2
    add3 size(int) data1 //swap

    //5
    add1 size(int) data2
    add2 size(int) data2
    //delete add3 (parametre)

    //6
    add1 size(int) data2
    //delete add2 (temporaire)
    Avec elision :
    //1
    add1 size(int) data1 //ctor new

    //2
    add1 size(int) data1
    add2 size(int) data2 //ctor new

    //3
    add1 size(int) data1
    add2 size(int) data2
    //copy-ctor elide

    //4
    add1 size(int) data2 //swap
    add2 size(int) data1 //swap
    //la fonction utilise directement add2

    //5
    add1 size(int) data2
    add2 size(int) data1
    //rien ne se passe

    //6
    add1 size(int) data2
    //delete add2 (temporaire)
    Je ne vois pas où il y aurait fuite mémoire.

    Pour le second code, le raisonnement est le même.

  20. #40
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Tu commence à approcher du problème, mais tu n'as apparemment toujours pas de vue d'ensemble.

    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.

    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

    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.

    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)

    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.

    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.

    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.

    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)
    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

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