:arrow: avais tu pensé à faire tes tests en mode release :question: (visual studio est connu pour faire beaucoup de choses en plus en mode débug ;))[/QUOTE]
je ne suis pas sous Visual C ! je suis sur Mac (et accessoirement sous Linux) ! : GCC !
Version imprimable
:arrow: avais tu pensé à faire tes tests en mode release :question: (visual studio est connu pour faire beaucoup de choses en plus en mode débug ;))[/QUOTE]
je ne suis pas sous Visual C ! je suis sur Mac (et accessoirement sous Linux) ! : GCC !
Koala01,
Désolé mais j'ai refait les tests sur un release :
operator= traditionnel :
durée 0.132424
durée 0.133042
durée 0.132578
copy and swap :
durée 0.149837
durée 0.149483
durée 0.150323
A noter également quand même qu'il n'y a pas pour l'instant de gestion d'erreur dans la fonction 'operator=' standard qui va sans doute être freinée par cet ajout. Mais si tu regardes les 2 fonctions, il est évident que la version classique est plus efficace, les affectations sont directes alors que la version swap passe forcément par une variable intermédiaire donc une affectation supplémentaire et des appels de fonction supplémentaires. Ce qui me surprend ce sont tes résultats qui paraissent dire le contraire. Je pense que ça dépend probablement du compilateur.
gl : la signature de la fonction 'operator=' est différente lorsque tu utilises le "copy and swap". Dans ce cas, tu ne passes pas le paramètre par référence (&m), parce que tu veux forcer la copie par le constructeur de copie pour disposer des valeurs (et non de l'adresse) de la matrice dans la pile pour effectuer ton swap.
Donc la fonction standard a pour signature
et la fonction 'copy and swap'Code:matrice& operator=(const matrice &m);
Quoiqu'il en soit, le 'copy and swap' est tout à fait génial. Quand on voit la difficulté de programmation de certaines fonctions 'operator=' qui sont un véritable casse tête, il n'y a pas de raison de s'en priver.Code:matrice& operator=(const matrice m);
En tout cas merci à tous, cette discussion m'a permis de faire une révision accélérée du C++ ! :ccool:
exact mais curieusement ça fonctionne très bien sur mon compilateur ??
Pour passer de la version classique à la version 'swap' je change juste le '&' !!!
En fait on ne touche pas à la matrice passée en argument mais seulement à sa copie dans la pile. Le mot 'const' ne se justifie donc effectivement pas dans cette configuration puisqu'on ne fournit pas l'adresse de l'argument mais une copie de sa valeur. Et apparemment le compilateur ne tient pas compte du 'const 'dans ce cas. Essaye sur Visual C, tu me diras si ça passe ou pas.
Pour les paramètres passés par valeur, const dans la déclaration ne veut rien dire, c'est la définition qui compte.
Par contre, sous Visual, si déclaration et définition ne sont pas identique, il y aura erreur d'édition de liens (du moins, sur les anciennes versions; je n'ai pas testé sur une version récente).
Exact, 'const' ne veut rien dire dans ce cas. Par contre mon compilo semble tout simplement ne pas en tenir compte.
Quand j'ai testé la methode 'copy and swap', je n'ai pas réécrit la déclaration de la fonction 'operator=' mais simplement supprimé le '&'. Le const est donc resté là par erreur. Le compilateur apparemment s'en fiche complètement. Ca semble logique comme comportement. Et il ne prend pas ombrage de la différence de signatures, je pense qu'il considère simplement tout passage par valeur comme 'const' pas défaut.
Avec ce code, moi, j'obtiens du code qui ne s'exécute pas, ce qui est normal, puisque le constructeur par copie de matrice fait n'importe quoi (delete sur un pointeur non initialisé).
Sinon, j'ai enlevé la copie inutile dans l'opérateur= de matrice, j'ai ajouté le destructeur qui manquait, j'ai joué sur la taille des matrices, voir si ça changeait des choses, et surtout, fidèle à mon idée, j'ai fait une troisième version avec vector. Les résultats sont clairs :
Pourquoi une telle différence ? Je pense que c'est avant tout parce que pour le vector, sans rien faire, on bénéficie de la sémantique de déplacement.Code:
1
2
3
4
5
6
7
8
9
10 exÚcution 0 : non swap = 23.394 swap = 22.75 vector = 2.608 exÚcution 1 : non swap = 23.347 swap = 22.782 vector = 2.595 exÚcution 2 : non swap = 23.347 swap = 22.797 vector = 2.604 exÚcution 3 : non swap = 23.349 swap = 22.779 vector = 2.603 exÚcution 4 : non swap = 23.352 swap = 22.759 vector = 2.602 exÚcution 5 : non swap = 23.362 swap = 22.811 vector = 2.596 exÚcution 6 : non swap = 23.35 swap = 22.755 vector = 2.598 exÚcution 7 : non swap = 23.341 swap = 22.773 vector = 2.599 exÚcution 8 : non swap = 23.376 swap = 22.756 vector = 2.598 exÚcution 9 : non swap = 23.348 swap = 22.757 vector = 2.601
Alors, certes, c'est "de la triche", mais en fait, pas tant que ça : Mon but était de prouver que du code écrit plus naturellement, plus simplement, avec moins de risques de bugs (j'en ai quand même trouvé dans les deux versions précédentes...) était aussi plus rapide quand on réutilisait des composants prévus pour.
Mon code :
Code:
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 #include <iostream> #include <iomanip> #include <ctime> #include <algorithm> #include <vector> class matrice{ public : matrice() { _li=0; _col=0; _val = 0;}; matrice(int li, int col); matrice(const matrice &m); // cons de copie ~matrice(); float& operator()(int li, int col); // setter float operator()(int li, int col) const; // getter matrice& operator=(matrice const &m); matrice operator+(const matrice & m2); int lignes(){ return _li;}; int colonnes(){ return _col;}; private : int _li, _col; float *_val; }; class matricesc{ public : matricesc() { _li=0; _col=0; _val = 0;}; matricesc(int li, int col); matricesc(const matricesc & m); // cons de copie ~matricesc(); float& operator()(int li, int col); // setter float operator()(int li, int col) const; // getter matricesc& operator=( matricesc m); matricesc operator+(const matricesc & m2); int lignes(){ return _li;}; int colonnes(){ return _col;}; private : void swap(matricesc & other); int _li, _col; float *_val; }; class matricev{ public : matricev() : _li(0), _col(0){} matricev(int li, int col); float& operator()(int li, int col); // setter float operator()(int li, int col) const; // getter matricev operator+(const matricev & m2); int lignes(){ return _li;}; int colonnes(){ return _col;}; private : int _li, _col; std::vector<float> _val; }; struct times{ clock_t first; clock_t second; clock_t third; }; int const taille=100; template<class Matrice> void test(clock_t &result) { Matrice m(taille,taille); Matrice n; for(int i = 0; i<taille; i++) for(int j=0; j<taille; j++) m(i,j) = i*taille + j; clock_t t; t = clock(); // initialisation T0 for (int i = 0; i<1000000; i++) n = m; t = clock()-t; // t1 -t0 => nobre de clicks par seconde result = t; } int main(){ std::vector <times> tab; for(int turn = 0;turn<10;++turn){ times currentTime; test<matrice>(currentTime.first); test<matricesc>(currentTime.second); test<matricev>(currentTime.third); tab.push_back(currentTime); } for(size_t i = 0;i< tab.size(); ++i){ double first = (double)tab[i].first/CLOCKS_PER_SEC; double second = (double)tab[i].second/CLOCKS_PER_SEC; double third = (double)tab[i].third/CLOCKS_PER_SEC; std::cout<< "exécution " <<std::setw(5) <<i << " : non swap = " << std::setw(8) << first << " swap = " << std::setw(8) << second << " vector = " << std::setw(8) << third << std::endl; } return 0; } /// ---------------------------- matrice matrice::matrice(int li , int col){ _li = li; _col= col; _val = new float[_li * _col]; } matrice::matrice(const matrice &m){ _li = m._li; _col = m._col; int n = _li*_col; _val = new float[n]; for(int i=0; i<n; i++) _val[i] = m._val[i]; } matrice matrice::operator+(const matrice &m2){ matrice temp(*this); int n = temp._li * temp._col; for(int i = 0; i<n; i++) temp._val[i] = _val[i] + m2._val[i]; return temp; } matrice &matrice::operator=(matrice const &m){ if(this !=&m) { delete [] _val; _li = m._li; _col = m._col; int n = _li*_col; _val = new float[n]; for(int i = 0; i<n; i++) _val[i] = m._val[i]; } return *this; } matrice::~matrice() { delete[] _val; } float& matrice::operator()(int l, int c){ return _val[l*_col+c]; } float matrice::operator()(int l, int c)const{ return _val[l*_col+c]; } /// ---------------------------- matricesc matricesc::matricesc(int li , int col){ _li = li; _col= col; _val = new float[_li * _col]; } matricesc::matricesc(const matricesc &m){ _li = m._li; _col = m._col; int n = _li*_col; _val = new float[n]; for(int i=0; i<n; i++) _val[i] = m._val[i]; } matricesc matricesc::operator+(const matricesc &m2){ matricesc temp(*this); int n = temp._li * temp._col; for(int i = 0; i<n; i++) temp._val[i] = _val[i] + m2._val[i]; return temp; } matricesc::~matricesc() { delete[] _val; } matricesc &matricesc::operator=( matricesc m){ m.swap(*this); return *this; } void matricesc::swap(matricesc &m){ std::swap(_li, m._li); std::swap(_col, m._col); std::swap(_val, m._val); } float& matricesc::operator()(int l, int c){ return _val[l*_col+c]; } float matricesc::operator()(int l, int c)const{ return _val[l*_col+c]; } /// ---------------------------- matricev matricev::matricev(int li , int col) : _li(li), _col(col), _val(li*col) { } matricev matricev::operator+(const matricev &m2){ matricev temp(*this); int n = temp._li * temp._col; for(int i = 0; i<n; i++) temp._val[i] = _val[i] + m2._val[i]; return temp; } float& matricev::operator()(int l, int c){ return _val[l*_col+c]; } float matricev::operator()(int l, int c)const{ return _val[l*_col+c]; }
Koala01,
Ton code beugue chez moi. Pas le temps aujourd'hjui de regarder ca de plus près.
J'ai vérifié mes fonctions , actuellement elles sont identiques à celles que tu utilises. J'ai été intéressé par l'utilisation de la classe vector.
Merci d'optimiser mon code !
Je te renvoies, sur ce point, à cette discussion dans laquelle on précise que les deux possibilités sont tout à fait bonne, bien que l'opérateur = prenant un objet permette le cas échéant de profiter d'une élision de la copie.
Comme je l'expliquais dans cette discussion, l'élision de copie n'est de toutes manières possible que si le type ne doit pas se charger d'allocations dynamiques, et n'est donc pas envisageable dans le cas présent ;)
J'ai donc, par acquit de conscience, vérifié en modifiant l'opérateur d'affectation de manière à ce qu'il prenne une référence constante sur un objet de type matricsc (qui est le type qui utilise le copy and swap), et les résultats restent malgré tout fort similaires, même si certains ratios sont juste inférieurs à 2 (de l'ordre de 1.8 à 1.9) ;)
Mais je ne vois pas quel biais j'aurais pu introduire en passant l'objet par valeur (ce qui implique donc une copie d'office) plutôt qu'en le passant par référence ;)
Au temps pour moi, tu parlais de l'opérateur de martice et non de matricesc...
Tout ce que j'ai donc remodifié le code afin de prendre ton avis en compte, de manière à permettre d'éviter la copie en cas d'auto affectation.
En mode débug, on observe bel et bien une inversion des ratios (la version sans copie and swap étant plus rapide avec des ratios allant de 1.02 à 1.1), par rapport au mode release (pour lequel la version copy and swap est plus rapide avec des ratio compris entre 1.15 et 1.38).
Cela nous mène donc à la deuxième règle de base des benchmarks (que peter ne semble pas avoir pris en compte): un bench s'effectue en mode release et non en mode debug ;)
PS: j'ai modifié le code de mon intervention précédente pour tenir compte des modifications ;)
Koala01,
Je ne veux pas te harceler ni chercher à tout prix à avoir raison. Tu es bien plus fort que moi en programmation, c'est évident.
Ton code ne fonctionne pas chez moi. J'ai donc repris ta programmation de la classe matricev et j'ai retesté une fonction 'main' juste un peu modifiée. J'ai ajouté une affectation à l'intérieur de la boucle de 1 000 000 itérations pour être sûr que le compilateur n'effectue pas une optimisation du style
à la place deCode:
1
2
3
4 n=m; for(int i = 0; i<1000000; i++) ;
Voici le code de main. Pour la classe matrice je me suis contenté de faire un copier-coller de ta classe et de changer 'matricev' en 'matrice'.Code:
1
2
3 for(int i = 0; i<1000000; i++) n=m;
Main
matrice.hCode:
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 #include <iostream> #include <ctime> #include "matrice.h" using namespace std; int main() { matrice m(3,3); matrice n; for(int i = 0; i<3; i++) for(int j=0; j<3; j++) m(i,j) = i*3 + j; clock_t t; for(int times=0; times <5; times++) { t = clock(); // initialisation T0 for (int i = 0; i<1000000; i++) { m(1,0) = float(i); n = m; } t = clock()-t; // t1 -t0 => nobre de clicks par seconde float d = (float)t/CLOCKS_PER_SEC; // nombre de secondes std::cout <<"durée " << d << std::endl; } return 0; }
matrice.cppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 #include <algorithm> #include <vector> #include <iostream> #include <iomanip> class matrice{ friend std::ostream& operator<<( std::ostream& flux, matrice &m); public : matrice() : _li(0), _col(0){} matrice(int li, int col); float& operator()(int li, int col); // setter float operator()(int li, int col) const; // getter matrice operator+(const matrice & m2); int lignes(){ return _li;}; int colonnes(){ return _col;}; private : int _li, _col; std::vector<float> _val; };
Voici les résultats en mode release que j'ai parfaitement pris en compte ! J'ai fait un copier-coller à partir du terminal.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #include "matrice.h" /// ---------------------------- matrice matrice::matrice(int li , int col) : _li(li), _col(col), _val(li*col) { } float& matrice::operator()(int l, int c){ return _val[l*_col+c]; } float matrice::operator()(int l, int c)const{ return _val[l*_col+c]; }
1- fonction operator=() classique
2- fonction style 'copie and swap'Citation:
Last login: Sun May 26 17:16:23 on ttys000
/Users/pierre/Desktop/classique ; exit;
imac-de-pierre:~ pierre$ /Users/pierre/Desktop/classique ; exit;
durée 0.160282
durée 0.159449
durée 0.15905
durée 0.157562
durée 0.158578
logout
[Opération terminée]
3- matrice programmée avec vector<float>Citation:
Last login: Sun May 26 17:33:57 on ttys000
/Users/pierre/Desktop/copy_and_swap ; exit;
imac-de-pierre:~ pierre$ /Users/pierre/Desktop/copy_and_swap ; exit;
durée 0.158738
durée 0.15834
durée 0.158691
durée 0.159487
durée 0.159779
logout
[Opération terminée]
Quand j'aurais le temps, je transfers tout ça sur linux et je recompile en linux G++.Citation:
Last login: Sun May 26 17:36:02 on ttys000
imac-de-pierre:~ pierre$ /Users/pierre/Desktop/test_vector ; exit;
durée 0.165899
durée 0.165483
durée 0.165555
durée 0.165397
durée 0.165395
logout
[Opération terminée]
Salut Koala01 !
Quelques remarques concernant ton code :
- tu t'es vraiment donné le beau rôle en saisissant une matrice improbable de 100 X 100 éléments. Recopie de 10 000 éléments au lieu de 9 ! Malgré tout je joue le jeu.
J'ai été surpris par la différence existant entre la version <vector> et les deux autres. La surprise est d'autant plus justifiée que plus la matrice est grosse plus le temps mis par ta programmation vecteur est courte !!! Ca sent l'optimisation à plein nez !
J'ai donc repris le programme et apporté les modification suivantes :
- d'abord j'ai éliminé la boucle de 100000 itérations et remplacé par une affectation de 10 matrices en série :
de cette façon seules les fonctions d'affectation sont prises en compte.Code:
1
2
3
4
5 Matrice n[10]; n[0] = m; for(int i=1; i<10; i++) n[i] = n[i-1];
J'ai "un peu" optimisé les fonctions 'operator+' du fichier matricecs et 'operator=" du fichier matrice en remplaçant la copie du tableau par std::copy :
Le temps n'est plus en secondes mais en clics.Code:
1
2
3
4 /*for(int i=0; i<n; i++) _val[i] = m._val[i];*/ std::copy(m._val, m._val+n, _val);
J'ai fait la moyenne de 20 tests en éliminant le premier qui est constamment très élevé.
Le résultat est sans appel !
La version classique l'emporte (de peu quand même) devant le 'copy and swap' et la version <vector> est dernière !
Peter : jeu set et match ! :yaisse2:Citation:
Last login: Mon May 27 22:26:18 on ttys001
/Users/pierre/Desktop/test_vector ; exit;
imac-de-pierre:~ pierre$ /Users/pierre/Desktop/test_vector ; exit;
exécution 0 : non swap = 399 swap = 57 vector = 56
exécution 1 : non swap = 57 swap = 56 vector = 59
exécution 2 : non swap = 58 swap = 55 vector = 58
exécution 3 : non swap = 54 swap = 61 vector = 62
exécution 4 : non swap = 56 swap = 57 vector = 57
exécution 5 : non swap = 57 swap = 55 vector = 58
exécution 6 : non swap = 57 swap = 56 vector = 56
exécution 7 : non swap = 54 swap = 55 vector = 57
exécution 8 : non swap = 55 swap = 55 vector = 67
exécution 9 : non swap = 55 swap = 55 vector = 56
exécution 10 : non swap = 53 swap = 69 vector = 64
exécution 11 : non swap = 55 swap = 54 vector = 56
exécution 12 : non swap = 62 swap = 57 vector = 57
exécution 13 : non swap = 56 swap = 57 vector = 58
exécution 14 : non swap = 57 swap = 56 vector = 57
exécution 15 : non swap = 58 swap = 66 vector = 67
exécution 16 : non swap = 58 swap = 58 vector = 56
exécution 17 : non swap = 57 swap = 55 vector = 55
exécution 18 : non swap = 55 swap = 55 vector = 60
exécution 19 : non swap = 57 swap = 55 vector = 56
exécution 20 : non swap = 53 swap = 57 vector = 67
moyenne sur 20 : non swap = 56.2 swap = 57.2 vector = 59.15
logout
[Opération terminée]
Bon je sors. :dehors:
Rendons à César-JolyLoic ce qui lui appartient, veux tu :question::D
C'est lui qui a présenté une version de code utilisant std::vector et qui a, effectivement, travaillé sur des matrices de 100*100 ;)
Ceci dit, en utilisant les bonnes méthodes de la classe vector (comme le constructeur prenant un nombre d'éléments ou la fonction reserve si l'on doit attendre d'avoir une idée relativement précise du nombre d'éléments que l'on voudra placer dedans), la création du tableau sous jascent est sensée être en temps constant, et la copie peut (ou devrait pouvoir) se faire "d'une traite" avec une fonction de l'ordre des memcpy et autres ;)
Il n'est donc pas forcément impensable que cela corresponde effectivement à une optimisation réelle.
Ceci dit, la réduction du code à écrire est tout à fait normale, car la classe std::vector est prévue pour être copiable de manière transparente pour l'utilisateur.
A partir de là, tout ce qui peut poser problème en terme de gestion dynamique de la mémoire peut, purement et simplement, être "oublié" par l'utilisateur alors se concentrer sur les problèmes propres au projets sur lequel il travaille.
C'est (en partie du moins) ce que l'on appelle l'encapsulation, ce qui permet la ré-utilisabilité du code et, surtout, le propre de n'importe quelle bibliothèque, qu'elle soit écrite en C ou en C++, fournie par le langage ou créée de tes propres mains ;)
La grosse différence qui existera entre une bibliothèque comme la S(T)L et ta bibliothèque personnelle étant que la S(T)L est "martyrisée" par un nombre de personnes qui permet de rapidement trouver les erreurs éventuelles, voire, de proposer des optimisations, justement.
[EDIT]Note que, si c'est pour n'avoir une différence que de dix ticks, j'aime autant prendre la version qui utilise des std::vector car elle est vraiment de nature à m'éviter bien des cheveux blancs, alors que pour voir une différence significative (allez, mettons d'une seconde), il faudra faire de très nombreuses itérations ;)
Tout à fait d'accord avec toi. En plus la STL a l'air d'être sacrément optimisée ! j'ai d'abord tenté d'optimiser la copie en programmant moi même la fonction copy de façon efficace, je pense.
Peux pas faire mieux ! :? J'ai été très déçu ! snif !Code:
1
2
3
4
5 inline void copy(float *deb, float *fin, float *dest){ while(deb != fin) *dest++ = *deb++; }
Résultat : 360 clicks au lieu de 56 avec la fonction std::copy ! (J'aimerais bien voir le source de cette fonction !!!)
Il faudrait voir l'optimisation du code par le compilateur mais je ne suis pas sûr d'arriver au même résultat. Autant utiliser la STL.
Bien cordialement.
Peter.
La version "artisanale" de operator= est-elle exception-safe? (pour chaque point susceptible de lancer une exception, pas de fuite ni de risque d'état incohérent de l'objet)
(pour un truc qui ne fait qu'une seule allocation, il y a des chances que oui)
Médinoc,
A priori je dirais que oui. La programmation est tout de même très 'standard', voire basique.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 matrice &matrice::operator=(matrice const &m){ if(this !=&m) { delete [] _val; _li = m._li; _col = m._col; int n = _li*_col; _val = new float[n]; std::copy(m._val, m._val+n, _val); } return *this; }
Heuuu... juste un truc: que se passe-t-il si (pour une raison ou une autre) new float[n] vient à échouer :question: :aie:
C'est très bien, tu évites le problème de l'auto affectation, mais... tu n'as strictement aucune certitude quant au fait que l'allocation dynamique réussira.
Le problème sera alors que... tu as perdu toutes les valeurs précédentes vu que tu en a déjà libéré la mémoire(re :aie:)
Mon cher Koala01,
je ne suis pas sûr que tu aies bien vu sur quel forum nous nous trouvons ! :(
Suis mon regard tout là haut :
:DCitation:
Forum du club des développeurs et IT Pro > C et C++ > C++ > Débuter
Ou alors c'est une forme de bizutage pour les nouveaux ?
Je suis un amateur (pas informaticien mais médecin !) qui n'a plus écrit une seule ligne de code depuis plus de 20 ans. En outre, comme je l'ai dit plus haut je ne suis vraiment pas un spécialiste du c++.
J'ai écrit cette classe pour me remémorer de très vieilles notions oubliées et pour tenter de me souvenir des pièges comme l'utilisation d'un pointeur non attribué ou le renvoi d'une référence sur un objet temporaire. Ca commence à revenir comme tu vois.
Laisse moi encore un peu de temps et je te fais la version avec les throw et les catch qui vont bien.
Mais vu que je n'ai nullement l'intention de me lancer dans un projet commercial de plusieurs années, le fait que ça risque de planter une fois par échec de l'allocation dynamique ne m'empêche pas de dormir ! :oops:
Merci tout de même pour tes conseils.:ave:
Vu qu'ici seul le new float[n] peut lancer une exception, ce code peut être rendu exception-safe sans copy-and-swap facilement: Il suffit de mettre l'allocation en premier.
Mais dès qu'il y a deux points capable de lancer des exceptions et deux ressources à gérer, c'est beaucoup plus dur. Une simple classe contenant deux matrices n'aura pas un opérateur = exception-safe sans une grande prise de tête: Avec l'implémentation naîve ci-dessous, la seconde matrice peut lancer une exception après que la première ait été affectée!
L'emploi du copy-and-swap résoudra le problème.Code:
1
2
3
4
5 DeuxMatrices & DeuxMatrices::operator=(DeuxMatrices const &src) { mat1 = src.mat1; mat2 = src.mat2; //Si on a une exception ici, crac! }
Ne t'en fais pas, je sais parfaitement dans quelle section nous nous trouvons ;)
Plutôt que de te répondre "sêchement" quelque chose du genre de "ben non, justement, tu te goures", je te pose une question pour te permettre de suivre le raisonnement par toi même.
Je reconnais que ton approche qui consiste à évite l'auto acceptation est correcte, mais que bah, voilà... tu n'as strictement aucune garantie de réussite à l'appel de new.
Quelque part, le fait que new lance une exception est ici ce qui peut encore t'arriver de mieux car ton application "planterait" purement et simplement.
Simplement, ta réponse "A priori je dirais que oui." n'est déjà pas bonne en soi, et cela méritait, même dans la section "débuter" d'être mis en évidence et d'être un minimum expliqué.
Je ne voulais absolument pas t'agresser -- en me connaissant un peu, tu te rendras compte que ce n'est pas mon style, même si je suis parfois un peu direct -- et si tu l'as ressenti comme cela, je t'en fais mes plus plates excuses.
Cependant, il faut savoir, vu que tu as mis les try catch sur le tapis, que leur utilisation, en l'état actuel de ton code et de l'ordre des instructions fera encore pis que mieux, car, si elle pourra sans doute éviter que ton application ne plante, elle laissera ton objet dans un état incohérent, au départ duquel il te sera impossible de revenir à "l'état initial" (cf la dernière ligne de mon intervention précédente) et que tu te trouveras donc sur une voie toute tracée qui, tôt ou tard, mènera à un comportement indéfini (vraisemblablement une erreur de segmentation, vu que c'est généralement ce qui arrive quand on essaye de déréférencer un pointeur invalide).
Ce genre de problème est généralement très difficile à résoudre, pour la simple et bonne raison qu'il peut survenir très loin du point où la cause prend naissance.
Tant qu'à se remémorer les base, autant essayer de se rappeler les problèmes sous-jacents, tu ne crois pas :question: