D'ailleurs, "exception-safe" ne signifie pas que le code intercepte les exceptions; seulement qu'une exception ne causera pas de fuite ou d'état incohérent.
Version imprimable
D'ailleurs, "exception-safe" ne signifie pas que le code intercepte les exceptions; seulement qu'une exception ne causera pas de fuite ou d'état incohérent.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 matrice &matrice::operator=(matrice const &m) { if(this !=&m) { int n = m._li*m._col; float *newVal = new float[n]; //L'allocation en premier //À partir d'ici, plus rien n'est censé pouvoir jeter d'exceptions delete [] _val; _li = m._li; _col = m._col; _val = newVal; std::copy(m._val, m._val+n, _val); } return *this; }
En fait, ta logique pour l'instant se résume à quatre points:
Comme je te l'ai indiqué, une fois que tu as fait delete [] _val;, c'est foutu : les données qui étaient contenues dans l'espace mémoire alloué à _val sont perdues et irrécupérables.
- vérifier l'auto affectation : c'est très bien
- libération de la mémoire : ca coince
- allocation dynamique de la mémoire : c'est préférable, mais déjà trop tard
- copie des données: c'est le moment où jamais
Du coup, si _val =new float[n] vient à échouer, le mieux est encore que ton application plante maintenant.
La solution consiste "simplement" à modifier l'ordre de ces différentes étapes:
Au final, cela ressemblerait à quelque chose comme
- tu fais le test d'auto affectation, ca, ca ne change pas.
- Tu alloues la mémoire à une variable temporaire float * temp = new float[n];
- Si elle s'est bien passée; tu peux libérer la mémoire allouée à _valdelete [] _val;
- évidemment, tu donne la bonne adresse à _val _val = temp;
- tu termine en copiant les donnée (c'est l'instant ou jamais)
De cette manière, si "quelque chose" ne se passe pas bien lors du new, une exception (qu'il faudra récupérer par ailleurs) est lancée, mais ton objet reste dans un état cohérent (_val continue à pointer vers une adresse mémoire valide qui correspond bel et bien à un espace mémoire suffisant pour représenter _li * _col éléments).Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 matrice &matrice::operator=(matrice const &m){ if(this !=&m) { int n = m._li*m._col; float temp = new float[n]; delete [] _val; _li = m._li; _col = m._col; _val = temp; std::copy(m._val, m._val+n, _val); } return *this; }
Cela t'évitera bien des soucis ;)
[EDIT]Grilled : sur le fil, une fois n'est pas coutume... Ca devrait m'apprendre à écrire des romans :D
Get it !
ca y est , j'ai compris !
J'ai également bien compris les avantages du copy and swap et aussi de l'utilisation de la STL décrite par Koala !
Merci !
J'irai beaucoup plus loin que vous: le test d'auto-affectation ne sert à rien.
Il date d'une époque où quelqu'un avait trouvé un super hack à la question: <<mais que va-t-il se passer quand on écrit "a= a" ?>>. Plutôt que de proposer la bonne réponse qui est "on passe par un temporaire" il partait sur une feinte.
Et vu que c'est une feinte, c'est "cool" de se la péter avec (pardonnez le vocabulaire, et pourtant...), résultat la feinte se propage et la bonne approche non. Le comble, c'est que la bonne approche suivait la logique : "1- on réalise l'opération qui peut planter, 2- les opérations qui ne peuvent pas planter, 3- on commite". Logique, que l'on ne compris (et au sujet de laquelle on ne communiqua) que bien plus tard, et qui était au coeur de comment écrire des opérations exception-safe (qui donnent une garantie forte de surcroit).
Bref, 20 ans plus tard, on sait écrire du code exception-safe qui n'a plus besoin de faire le test d'auto-affectation. Quel mal y a-t-il donc à prendre ce petit cachet qui nous fait gagner du temps quand on écrit "a=a" ? Ben, il nous en fait perdre dans toutes les autres situations, ou plus exactement: tout le temps. Car on n'écrit jamais "a=a".
Nous sommes contents d'avoir pu t'aider.
Ceci dit, je voudrais quand même terminer par un conseil.
C++ est un langage particulièrement puissant mais cette puissance a un prix.
Le prix de cette puissance est que c'est également un langage particulièrement complexe.
A tel point que tu peux en arriver à le pratiquer pendant plusieurs années sans pour autant pouvoir estimer en "avoir fait le tour".
Le problème, c'est que, les erreurs (parfois insidieuses) se payeront beaucoup plus cher, et "cash" qu'avec d'autres langages.
La bonne optique est donc de se baser sur le fait qu'il ne sert à rien de vouloir (sans doute) mal refaire quelque chose qui existe et qui a été longuement éprouvé, ni de vouloir se croire meilleur que le compilateur.
Par contre, une très mauvaise idée qui est encore trop souvent véhiculée est de se dire "bah, je connais (plus ou moins) bien C, je n'ai qu'à adapter mes habitudes en C++, ca devrait être facile" parce que la plupart des habitudes "vitales" en C s'avèrent être "mortelles" en C++.
Par exemple, l'allocation dynamique de la mémoire(qui était l'un des points noirs de cette discussion) est un aspect fondamental à maitriser en C, alors que, tu t'en seras rendu compte, cette pratique s'avère extrêmement dangereuse en C++ (en fait, elle est aussi dangereuse en C, simplement, on n'a pas d'autre choix :aie:).
La règle de base que tu devrais donc appliquer est fort proche de "tant que tu peux éviter d'avoir recours à un pointeur (et / ou à l'allocation dynamique de la mémoire) évites les comme la peste".
Lorsqu'il s'agit "simplement" de gérer une collection d'objets, par exemple, tourne toi de manière systématique vers les collections de la S(T)L, car il y en a pour tous les gouts: Cela va du t'ableau d'éléments contigus en mémoire(comme celui que tu gère avec new float[n]) au tableau de bits, en passant par les listes, les files, les piles, les tableaux associatifs et bien plus encore ;)
Tu éprouveras bien plus de plaisir à apprendre en sachant que, déjà, ton code ne risque pas de "tomber en marche" parce que tu as "oublié" de vérifier l'espace mémoire dont tu disposais ;)
Bien sur, tu auras besoin des pointeurs et de l'allocation dynamique de la mémoire... Mais tu ne devra vraiment t'y intéresser que lorsque tu aborderas des concepts bien plus avancés comme l'héritage et le polymorphisme.
D'ici là, tu auras eu le temps de t'imprégner de la "logique du C++" ;)
Ensuite, garde en tête que, si le compilateur peut faire (automatiquement) quelque chose à ta place, il le fera selon toutes vraisemblance beaucoup mieux que si tu essayais de le faire toi même.
Tu ne tarderas sans doute pas à croiser la forme canonique orthodoxe de coplien (peut etre l'as tu déjà croisée d'ailleurs, vu que tu l'as mise en œuvre, à bon escient d'ailleurs :question:)
Mais il faut savoir que, tant que tu ne commences pas à "jouer" avec l'allocation dynamique de la mémoire, le compilateur sera parfaitement en mesure de la mettre en œuvre pour toi.
Dés lors, pourquoi se fatiguer à la mettre en place :question: d'autant plus qu'il existe (tu ne l'as peut etre pas encore remarqué par contre ;)) d'autres formes de Coplien, et qu'il faut arriver à déterminer celle qui correspond le mieux à tes besoins.
Mais cela encore, c'est un concept qui pourra attendre que tu t'attaques au paradigme Orienté Objets, et je n'en parlerai pas d'avantage ici.
Là dessus, je te laisse bien volontiers à ta "remise à jour".
Bon courage ;)