Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement. Se contenter
de retourner *this au lieu d'exécuter plein de code me paraît
tellement naturel.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité. Moi, je partirais
plutôt de l'idée que les auto-affectations sont des cas assez
rares, et je n'ajouterais pas du code pour les optimiser s'il
rallentissait aussi peu soit-il le cas où les objets n'étaient
pas identiques.
Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement. Se contenter
de retourner *this au lieu d'exécuter plein de code me paraît
tellement naturel.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité. Moi, je partirais
plutôt de l'idée que les auto-affectations sont des cas assez
rares, et je n'ajouterais pas du code pour les optimiser s'il
rallentissait aussi peu soit-il le cas où les objets n'étaient
pas identiques.
Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement. Se contenter
de retourner *this au lieu d'exécuter plein de code me paraît
tellement naturel.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité. Moi, je partirais
plutôt de l'idée que les auto-affectations sont des cas assez
rares, et je n'ajouterais pas du code pour les optimiser s'il
rallentissait aussi peu soit-il le cas où les objets n'étaient
pas identiques.
for ( iterator it=liste.begin() ; it!=liste.end() ; ++it )
Question à deux balles : A tous les coups, le compilo n'a aucun moyen
d'extraire la valeur de liste.end() une fois pour toute avant le début
de la boucle, du coup à chaque itération de la boucle le programme se
retape l'appel à liste.end() !
La question toute simple que je me pose est donc : est-ce que c'est pas
mieux de faireiterator end=liste.end()
for ( iterator it=liste.begin() ; it!=end ; ++it )
for ( iterator it=liste.begin() ; it!=liste.end() ; ++it )
Question à deux balles : A tous les coups, le compilo n'a aucun moyen
d'extraire la valeur de liste.end() une fois pour toute avant le début
de la boucle, du coup à chaque itération de la boucle le programme se
retape l'appel à liste.end() !
La question toute simple que je me pose est donc : est-ce que c'est pas
mieux de faire
iterator end=liste.end()
for ( iterator it=liste.begin() ; it!=end ; ++it )
for ( iterator it=liste.begin() ; it!=liste.end() ; ++it )
Question à deux balles : A tous les coups, le compilo n'a aucun moyen
d'extraire la valeur de liste.end() une fois pour toute avant le début
de la boucle, du coup à chaque itération de la boucle le programme se
retape l'appel à liste.end() !
La question toute simple que je me pose est donc : est-ce que c'est pas
mieux de faireiterator end=liste.end()
for ( iterator it=liste.begin() ; it!=end ; ++it )
je suis plutôt de l'avis de Alain, tester le cas d'auto-affectation me
parait même indispensable pour a) ne pas risquer de perdre les données,
et devrait être
if (ref != other.ref){
if (ref) ref->release();
ref = other.ref;
if (ref) ref->addRef();
}
(les if pouvant être mieux utilisés, ce n'est pas le point)
je suis plutôt de l'avis de Alain, tester le cas d'auto-affectation me
parait même indispensable pour a) ne pas risquer de perdre les données,
et devrait être
if (ref != other.ref){
if (ref) ref->release();
ref = other.ref;
if (ref) ref->addRef();
}
(les if pouvant être mieux utilisés, ce n'est pas le point)
je suis plutôt de l'avis de Alain, tester le cas d'auto-affectation me
parait même indispensable pour a) ne pas risquer de perdre les données,
et devrait être
if (ref != other.ref){
if (ref) ref->release();
ref = other.ref;
if (ref) ref->addRef();
}
(les if pouvant être mieux utilisés, ce n'est pas le point)
kanze wrote on 22/09/2006 18:29:Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement. Se contenter
de retourner *this au lieu d'exécuter plein de code me paraît
tellement naturel.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité. Moi, je partirais
plutôt de l'idée que les auto-affectations sont des cas assez
rares, et je n'ajouterais pas du code pour les optimiser s'il
rallentissait aussi peu soit-il le cas où les objets n'étaient
pas identiques.
je suis plutôt de l'avis de Alain, tester le cas d'auto-affectation me
parait même indispensable pour a) ne pas risquer de perdre les donnée s,
b) s'éviter de nombreux tests en remplacement de celui-ci; ces
points me paraissent vitaux par rapport à celui de
l'optimisation de l'opérateur.
détaillons: soit nous parlons de petite structure de données
de 4 ou 16 octets et le gain du test est négligeable mais
écrit-on son propre opérateur dans de tels cas?
soit la structure à copier est ""compliquée"" (et a priori
elle l'est pour mériter un opérateur explicite) et elle ne se
satisfait pas d'une copie membre à membre (je l'ai déjà dit).
ceci implique que soit ses membres réalisent eux-même les
tests de protection idoine,
exemple simple:
struct container {
std::string s;
container& operator= (const container& other){
s = other.s;
return *this;
}
};
l'auto-affectation est gérée par l'opérateur d'affectation de
string et tout va bien.
soit des tests sont réalisés sur chacun des membres ne pouvant
pas être auto-copiés, par exemple parce qu'il utilise un
mécanisme de reference counting, ie le code suivant est
invalide:
struct container {
rc_ptr* ref;
container& operator= (const container& other){
if (ref) ref->release();
// risque de destruction de this->ref == other.ref
ref = other.ref;
if (ref) ref->addRef();
return *this;
}
};
et devrait être
if (ref != other.ref){
if (ref) ref->release();
ref = other.ref;
if (ref) ref->addRef();
}
(les if pouvant être mieux utilisés, ce n'est pas le point)
soit (plus simplement) parce qu'une donnée membre est un pointeur
(basique, non un very_smart_ptr ni ne commence pas par détruire la
donnée pointée).
l'absence totale de tests à un niveau donné me parait valide
qui si toutes les données membres implémentent elles-même des
tests dans leur propre copie, or il y a bien un début (ces
classes de base), refuser systématiquement un test traitant
spécifiquement de l'auto-copie me parait dès lors non
pertinent.
kanze wrote on 22/09/2006 18:29:
Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement. Se contenter
de retourner *this au lieu d'exécuter plein de code me paraît
tellement naturel.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité. Moi, je partirais
plutôt de l'idée que les auto-affectations sont des cas assez
rares, et je n'ajouterais pas du code pour les optimiser s'il
rallentissait aussi peu soit-il le cas où les objets n'étaient
pas identiques.
je suis plutôt de l'avis de Alain, tester le cas d'auto-affectation me
parait même indispensable pour a) ne pas risquer de perdre les donnée s,
b) s'éviter de nombreux tests en remplacement de celui-ci; ces
points me paraissent vitaux par rapport à celui de
l'optimisation de l'opérateur.
détaillons: soit nous parlons de petite structure de données
de 4 ou 16 octets et le gain du test est négligeable mais
écrit-on son propre opérateur dans de tels cas?
soit la structure à copier est ""compliquée"" (et a priori
elle l'est pour mériter un opérateur explicite) et elle ne se
satisfait pas d'une copie membre à membre (je l'ai déjà dit).
ceci implique que soit ses membres réalisent eux-même les
tests de protection idoine,
exemple simple:
struct container {
std::string s;
container& operator= (const container& other){
s = other.s;
return *this;
}
};
l'auto-affectation est gérée par l'opérateur d'affectation de
string et tout va bien.
soit des tests sont réalisés sur chacun des membres ne pouvant
pas être auto-copiés, par exemple parce qu'il utilise un
mécanisme de reference counting, ie le code suivant est
invalide:
struct container {
rc_ptr* ref;
container& operator= (const container& other){
if (ref) ref->release();
// risque de destruction de this->ref == other.ref
ref = other.ref;
if (ref) ref->addRef();
return *this;
}
};
et devrait être
if (ref != other.ref){
if (ref) ref->release();
ref = other.ref;
if (ref) ref->addRef();
}
(les if pouvant être mieux utilisés, ce n'est pas le point)
soit (plus simplement) parce qu'une donnée membre est un pointeur
(basique, non un very_smart_ptr ni ne commence pas par détruire la
donnée pointée).
l'absence totale de tests à un niveau donné me parait valide
qui si toutes les données membres implémentent elles-même des
tests dans leur propre copie, or il y a bien un début (ces
classes de base), refuser systématiquement un test traitant
spécifiquement de l'auto-copie me parait dès lors non
pertinent.
kanze wrote on 22/09/2006 18:29:Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement. Se contenter
de retourner *this au lieu d'exécuter plein de code me paraît
tellement naturel.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité. Moi, je partirais
plutôt de l'idée que les auto-affectations sont des cas assez
rares, et je n'ajouterais pas du code pour les optimiser s'il
rallentissait aussi peu soit-il le cas où les objets n'étaient
pas identiques.
je suis plutôt de l'avis de Alain, tester le cas d'auto-affectation me
parait même indispensable pour a) ne pas risquer de perdre les donnée s,
b) s'éviter de nombreux tests en remplacement de celui-ci; ces
points me paraissent vitaux par rapport à celui de
l'optimisation de l'opérateur.
détaillons: soit nous parlons de petite structure de données
de 4 ou 16 octets et le gain du test est négligeable mais
écrit-on son propre opérateur dans de tels cas?
soit la structure à copier est ""compliquée"" (et a priori
elle l'est pour mériter un opérateur explicite) et elle ne se
satisfait pas d'une copie membre à membre (je l'ai déjà dit).
ceci implique que soit ses membres réalisent eux-même les
tests de protection idoine,
exemple simple:
struct container {
std::string s;
container& operator= (const container& other){
s = other.s;
return *this;
}
};
l'auto-affectation est gérée par l'opérateur d'affectation de
string et tout va bien.
soit des tests sont réalisés sur chacun des membres ne pouvant
pas être auto-copiés, par exemple parce qu'il utilise un
mécanisme de reference counting, ie le code suivant est
invalide:
struct container {
rc_ptr* ref;
container& operator= (const container& other){
if (ref) ref->release();
// risque de destruction de this->ref == other.ref
ref = other.ref;
if (ref) ref->addRef();
return *this;
}
};
et devrait être
if (ref != other.ref){
if (ref) ref->release();
ref = other.ref;
if (ref) ref->addRef();
}
(les if pouvant être mieux utilisés, ce n'est pas le point)
soit (plus simplement) parce qu'une donnée membre est un pointeur
(basique, non un very_smart_ptr ni ne commence pas par détruire la
donnée pointée).
l'absence totale de tests à un niveau donné me parait valide
qui si toutes les données membres implémentent elles-même des
tests dans leur propre copie, or il y a bien un début (ces
classes de base), refuser systématiquement un test traitant
spécifiquement de l'auto-copie me parait dès lors non
pertinent.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité.
Certes, mais quand même là, ça ne coûte pas cher. Sûrement
moins qu'un end() invoqué à tout bouts de champs si je peux me
permettre.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité.
Certes, mais quand même là, ça ne coûte pas cher. Sûrement
moins qu'un end() invoqué à tout bouts de champs si je peux me
permettre.
Le test coûte quand même du temps. En plus du temps qu'il te
faut autrement, s'il n'y a pas identité.
Certes, mais quand même là, ça ne coûte pas cher. Sûrement
moins qu'un end() invoqué à tout bouts de champs si je peux me
permettre.
Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement.
Ce qui complique le code, pour un résultat imperceptible dans la
plupart des cas.
Je ne trouve pas, au contraire. Je parle de la complication.Compliquer le code en rajoutant des variables ou des
structures "if" simplement dans l'idée que peut-être, un
jour, ça accélérera le programme, c'est de l'optimisation
prématurée.
Mais là ce n'est pas question d'optimisation, pas du tout. Il
est vrai que c'est venu dans la conversation dans un contexte
d'optimsation, mais ce n'est pas ce que je voulais dire en
disant que je ne partageias pas le point de vue de Herb
Sutter. C'est question de simplicité selon moi. C'est
tellement plus simple d'écrire
if(this == &src) return *this;
que de faire attention à ne pas libérer deux foix une même
ressource qui serait détenue par la classe.
Tu dis que ça complique le code, moi je
dis que ça le simplifie. Comme je l'ai dit je trouve ça tellement nat urel.
C'est la raison première. Reste les éventuelles constructions inutiles
des éventuels objets membres de la classe en cas d'auto-affectation.
Eviter ces constructions est un corollaire du test et vraiment je trouve
que ce n'est plus mal.
Sans doute qu'il y a des cas où l'on pourrait se dispenser du test.
Disons que j'ai l'habitude de toujours faire comme ça peut être un peu
par paresse aussi. Parce que je trouve justement que le coût du test est
négligeable tandis qu'il me simplifie la vie.
Mais vraiment je trouve pas que le code soit compliqué. Si je lis du
code écrit et que je vois
if(this == &src) return *this;
au début du code alors je suis sûr qu'il n'y a pas de problème.
Sinon faut tout lire en ayant le cas particulier de
l'auto-affectation en tête. Enfin ce n'est que mon avis :)
Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement.
Ce qui complique le code, pour un résultat imperceptible dans la
plupart des cas.
Je ne trouve pas, au contraire. Je parle de la complication.
Compliquer le code en rajoutant des variables ou des
structures "if" simplement dans l'idée que peut-être, un
jour, ça accélérera le programme, c'est de l'optimisation
prématurée.
Mais là ce n'est pas question d'optimisation, pas du tout. Il
est vrai que c'est venu dans la conversation dans un contexte
d'optimsation, mais ce n'est pas ce que je voulais dire en
disant que je ne partageias pas le point de vue de Herb
Sutter. C'est question de simplicité selon moi. C'est
tellement plus simple d'écrire
if(this == &src) return *this;
que de faire attention à ne pas libérer deux foix une même
ressource qui serait détenue par la classe.
Tu dis que ça complique le code, moi je
dis que ça le simplifie. Comme je l'ai dit je trouve ça tellement nat urel.
C'est la raison première. Reste les éventuelles constructions inutiles
des éventuels objets membres de la classe en cas d'auto-affectation.
Eviter ces constructions est un corollaire du test et vraiment je trouve
que ce n'est plus mal.
Sans doute qu'il y a des cas où l'on pourrait se dispenser du test.
Disons que j'ai l'habitude de toujours faire comme ça peut être un peu
par paresse aussi. Parce que je trouve justement que le coût du test est
négligeable tandis qu'il me simplifie la vie.
Mais vraiment je trouve pas que le code soit compliqué. Si je lis du
code écrit et que je vois
if(this == &src) return *this;
au début du code alors je suis sûr qu'il n'y a pas de problème.
Sinon faut tout lire en ayant le cas particulier de
l'auto-affectation en tête. Enfin ce n'est que mon avis :)
Si j'écris une classe qui définit l'opérator =,
l'auto-affectation je la teste systématiquement.
Ce qui complique le code, pour un résultat imperceptible dans la
plupart des cas.
Je ne trouve pas, au contraire. Je parle de la complication.Compliquer le code en rajoutant des variables ou des
structures "if" simplement dans l'idée que peut-être, un
jour, ça accélérera le programme, c'est de l'optimisation
prématurée.
Mais là ce n'est pas question d'optimisation, pas du tout. Il
est vrai que c'est venu dans la conversation dans un contexte
d'optimsation, mais ce n'est pas ce que je voulais dire en
disant que je ne partageias pas le point de vue de Herb
Sutter. C'est question de simplicité selon moi. C'est
tellement plus simple d'écrire
if(this == &src) return *this;
que de faire attention à ne pas libérer deux foix une même
ressource qui serait détenue par la classe.
Tu dis que ça complique le code, moi je
dis que ça le simplifie. Comme je l'ai dit je trouve ça tellement nat urel.
C'est la raison première. Reste les éventuelles constructions inutiles
des éventuels objets membres de la classe en cas d'auto-affectation.
Eviter ces constructions est un corollaire du test et vraiment je trouve
que ce n'est plus mal.
Sans doute qu'il y a des cas où l'on pourrait se dispenser du test.
Disons que j'ai l'habitude de toujours faire comme ça peut être un peu
par paresse aussi. Parce que je trouve justement que le coût du test est
négligeable tandis qu'il me simplifie la vie.
Mais vraiment je trouve pas que le code soit compliqué. Si je lis du
code écrit et que je vois
if(this == &src) return *this;
au début du code alors je suis sûr qu'il n'y a pas de problème.
Sinon faut tout lire en ayant le cas particulier de
l'auto-affectation en tête. Enfin ce n'est que mon avis :)
Quest-ce que tu reproches à
T& operator= (T const& src)
{
if (this == &src) return *this;
//Ici, le code
return *this;
}
Qu'il y a deux fois return *this ? Moi ça ne me gêne pas.
Note que le code
void T::Swap (T& autre) throw()
{...}
T& operator= (T const& src)
{
T nouveau (src);
Swap (nouveau);
return *this;
}
Moi aussi j'ai lu Sutter ;)
fonctionne avec ou sans le test, qui devient alors une
simple optimisation.
Sauf que la création du temporaire, à supposer que le
constructeur de copie existe (bon je pousse un peu, il y est
très très probablement ;)
peut te lever une exception qui dans le contexte de
l'auto-affectation a quand même un côté un peu idiot. Obstiné,
je préfère:
T& operator= (T const& src)
{
if (this == &src) return *this;
T nouveau (src);
Swap (nouveau);
return *this;
}
Quest-ce que tu reproches à
T& operator= (T const& src)
{
if (this == &src) return *this;
//Ici, le code
return *this;
}
Qu'il y a deux fois return *this ? Moi ça ne me gêne pas.
Note que le code
void T::Swap (T& autre) throw()
{...}
T& operator= (T const& src)
{
T nouveau (src);
Swap (nouveau);
return *this;
}
Moi aussi j'ai lu Sutter ;)
fonctionne avec ou sans le test, qui devient alors une
simple optimisation.
Sauf que la création du temporaire, à supposer que le
constructeur de copie existe (bon je pousse un peu, il y est
très très probablement ;)
peut te lever une exception qui dans le contexte de
l'auto-affectation a quand même un côté un peu idiot. Obstiné,
je préfère:
T& operator= (T const& src)
{
if (this == &src) return *this;
T nouveau (src);
Swap (nouveau);
return *this;
}
Quest-ce que tu reproches à
T& operator= (T const& src)
{
if (this == &src) return *this;
//Ici, le code
return *this;
}
Qu'il y a deux fois return *this ? Moi ça ne me gêne pas.
Note que le code
void T::Swap (T& autre) throw()
{...}
T& operator= (T const& src)
{
T nouveau (src);
Swap (nouveau);
return *this;
}
Moi aussi j'ai lu Sutter ;)
fonctionne avec ou sans le test, qui devient alors une
simple optimisation.
Sauf que la création du temporaire, à supposer que le
constructeur de copie existe (bon je pousse un peu, il y est
très très probablement ;)
peut te lever une exception qui dans le contexte de
l'auto-affectation a quand même un côté un peu idiot. Obstiné,
je préfère:
T& operator= (T const& src)
{
if (this == &src) return *this;
T nouveau (src);
Swap (nouveau);
return *this;
}
Je dois aussi confesser que pour mon malheur je fait beaucoup
de Java et c'est en codant des boucles sur des tableaux en
Java que j'ais pris l'habitude de sortir tableau.length de la
boucle. Et je me suis mis à faire l'équivalent en C++
Marrant. En Java, je me servais aussi des itérateurs. Et du
coup, la question ne se posait pas, parce qu'avec un itérateur
Java, un suffit.
Avec des containers, pas avec des tableaux.
Et la question des perfs avec les conteneurs,
et les transtypages permanents que ça implique.
Bouh... revenons au bon vieux C++ :)
Je dois aussi confesser que pour mon malheur je fait beaucoup
de Java et c'est en codant des boucles sur des tableaux en
Java que j'ais pris l'habitude de sortir tableau.length de la
boucle. Et je me suis mis à faire l'équivalent en C++
Marrant. En Java, je me servais aussi des itérateurs. Et du
coup, la question ne se posait pas, parce qu'avec un itérateur
Java, un suffit.
Avec des containers, pas avec des tableaux.
Et la question des perfs avec les conteneurs,
et les transtypages permanents que ça implique.
Bouh... revenons au bon vieux C++ :)
Je dois aussi confesser que pour mon malheur je fait beaucoup
de Java et c'est en codant des boucles sur des tableaux en
Java que j'ais pris l'habitude de sortir tableau.length de la
boucle. Et je me suis mis à faire l'équivalent en C++
Marrant. En Java, je me servais aussi des itérateurs. Et du
coup, la question ne se posait pas, parce qu'avec un itérateur
Java, un suffit.
Avec des containers, pas avec des tableaux.
Et la question des perfs avec les conteneurs,
et les transtypages permanents que ça implique.
Bouh... revenons au bon vieux C++ :)
On Fri, 22 Sep 2006 18:54:16 +0200, Alain Gaillard
:Bien sûr que la version de Fabien est meilleure, mais au
niveau de l'élégance du code seulement.
Pas seulement. Ma version évite ça :
iterator end=liste.end()
for ( iterator it=liste.begin() ; it!=end ; ++it )
...
const_iterator end= mon_tableau.end();
for ( const_iterator it=mon_tableau.begin() ; it!=end ; ++it )
On Fri, 22 Sep 2006 18:54:16 +0200, Alain Gaillard
<alain_gaillard28@hotmail.fr>:
Bien sûr que la version de Fabien est meilleure, mais au
niveau de l'élégance du code seulement.
Pas seulement. Ma version évite ça :
iterator end=liste.end()
for ( iterator it=liste.begin() ; it!=end ; ++it )
...
const_iterator end= mon_tableau.end();
for ( const_iterator it=mon_tableau.begin() ; it!=end ; ++it )
On Fri, 22 Sep 2006 18:54:16 +0200, Alain Gaillard
:Bien sûr que la version de Fabien est meilleure, mais au
niveau de l'élégance du code seulement.
Pas seulement. Ma version évite ça :
iterator end=liste.end()
for ( iterator it=liste.begin() ; it!=end ; ++it )
...
const_iterator end= mon_tableau.end();
for ( const_iterator it=mon_tableau.begin() ; it!=end ; ++it )
Mais pourquoi est-ce que je libérer deux foix une même
ressource ?
Et c'est là, le problème. Considérons une classe simple qui fait
une copie profonde de quelque chose :
MaClasse&
MaClasse::operator=( MaClasse const& other )
{
if ( this == &other ) return * this ;
delete myPtr ;
myPtr = new ( MemberClass( *other.myPtr ) ) ;
return *this ;
}
Tu vois le test. Tu es donc sûr qu'il n'y a pas de problème.
Et
qu'est-ce qui se passe si le constructeur de copie de
MemberClass lève une exception ? Ou si l'operator new lève
std::bad_alloc ? Quel est l'état de l'objet alors ?
Pour se parer de ces problèmes, on est amené à faire plutôt
quelque chose du genre :
MaClasse&
MaClasse::operator=( MaClasse const& other )
{
if ( this == &other ) return * this ;
MemberClass* tmp = new ( MemberClass( *other.myPtr ) ) ;
delete myPtr ;
myPtr = tmp ;
return *this ;
}
Et du coup, le test de l'auto-affectation devient superflu.
(Dans les discussions, 'était moi qui a noté que la nécessité
d'un tel test était même un signal que l'opérateur a des
problèmes avec les exceptions.)
Note bien que si tu as un constructeur de copie et un
destructeur qui va bien, et que tu définisses une fonction swap
du genre :
void
MaClasse::swap( MaClasse& other )
{
std::swap( myPtr, other.myPtr ) ;
}
qui est garanti de ne pas lever d'exception, tu n'as qu'à
écrire :
MaClasse&
MaClasse::operator( MaClasse const& other )
{
MaClasse tmp( other ) ;
swap( tmp ) ;
return *this ;
}
Si tu utilises les idiomes consacrés, tous qui assure la copie
et l'allocation des nouvelles ressources avant de modifier
quoique ce soit dans l'objet cible, l'auto-affectation
fonctionne de soi, sans que tu y penses.
Et si tu ne le fais
pas, tu n'as pas que l'auto-affectation auquel penser ; tu dois
considérer à chaque instant ce qui ce passe si l'opération en
question lève une exception. Et crois-moi, c'est bien plus
casse-tête que l'auto-affectation.
Mais pourquoi est-ce que je libérer deux foix une même
ressource ?
Et c'est là, le problème. Considérons une classe simple qui fait
une copie profonde de quelque chose :
MaClasse&
MaClasse::operator=( MaClasse const& other )
{
if ( this == &other ) return * this ;
delete myPtr ;
myPtr = new ( MemberClass( *other.myPtr ) ) ;
return *this ;
}
Tu vois le test. Tu es donc sûr qu'il n'y a pas de problème.
Et
qu'est-ce qui se passe si le constructeur de copie de
MemberClass lève une exception ? Ou si l'operator new lève
std::bad_alloc ? Quel est l'état de l'objet alors ?
Pour se parer de ces problèmes, on est amené à faire plutôt
quelque chose du genre :
MaClasse&
MaClasse::operator=( MaClasse const& other )
{
if ( this == &other ) return * this ;
MemberClass* tmp = new ( MemberClass( *other.myPtr ) ) ;
delete myPtr ;
myPtr = tmp ;
return *this ;
}
Et du coup, le test de l'auto-affectation devient superflu.
(Dans les discussions, 'était moi qui a noté que la nécessité
d'un tel test était même un signal que l'opérateur a des
problèmes avec les exceptions.)
Note bien que si tu as un constructeur de copie et un
destructeur qui va bien, et que tu définisses une fonction swap
du genre :
void
MaClasse::swap( MaClasse& other )
{
std::swap( myPtr, other.myPtr ) ;
}
qui est garanti de ne pas lever d'exception, tu n'as qu'à
écrire :
MaClasse&
MaClasse::operator( MaClasse const& other )
{
MaClasse tmp( other ) ;
swap( tmp ) ;
return *this ;
}
Si tu utilises les idiomes consacrés, tous qui assure la copie
et l'allocation des nouvelles ressources avant de modifier
quoique ce soit dans l'objet cible, l'auto-affectation
fonctionne de soi, sans que tu y penses.
Et si tu ne le fais
pas, tu n'as pas que l'auto-affectation auquel penser ; tu dois
considérer à chaque instant ce qui ce passe si l'opération en
question lève une exception. Et crois-moi, c'est bien plus
casse-tête que l'auto-affectation.
Mais pourquoi est-ce que je libérer deux foix une même
ressource ?
Et c'est là, le problème. Considérons une classe simple qui fait
une copie profonde de quelque chose :
MaClasse&
MaClasse::operator=( MaClasse const& other )
{
if ( this == &other ) return * this ;
delete myPtr ;
myPtr = new ( MemberClass( *other.myPtr ) ) ;
return *this ;
}
Tu vois le test. Tu es donc sûr qu'il n'y a pas de problème.
Et
qu'est-ce qui se passe si le constructeur de copie de
MemberClass lève une exception ? Ou si l'operator new lève
std::bad_alloc ? Quel est l'état de l'objet alors ?
Pour se parer de ces problèmes, on est amené à faire plutôt
quelque chose du genre :
MaClasse&
MaClasse::operator=( MaClasse const& other )
{
if ( this == &other ) return * this ;
MemberClass* tmp = new ( MemberClass( *other.myPtr ) ) ;
delete myPtr ;
myPtr = tmp ;
return *this ;
}
Et du coup, le test de l'auto-affectation devient superflu.
(Dans les discussions, 'était moi qui a noté que la nécessité
d'un tel test était même un signal que l'opérateur a des
problèmes avec les exceptions.)
Note bien que si tu as un constructeur de copie et un
destructeur qui va bien, et que tu définisses une fonction swap
du genre :
void
MaClasse::swap( MaClasse& other )
{
std::swap( myPtr, other.myPtr ) ;
}
qui est garanti de ne pas lever d'exception, tu n'as qu'à
écrire :
MaClasse&
MaClasse::operator( MaClasse const& other )
{
MaClasse tmp( other ) ;
swap( tmp ) ;
return *this ;
}
Si tu utilises les idiomes consacrés, tous qui assure la copie
et l'allocation des nouvelles ressources avant de modifier
quoique ce soit dans l'objet cible, l'auto-affectation
fonctionne de soi, sans que tu y penses.
Et si tu ne le fais
pas, tu n'as pas que l'auto-affectation auquel penser ; tu dois
considérer à chaque instant ce qui ce passe si l'opération en
question lève une exception. Et crois-moi, c'est bien plus
casse-tête que l'auto-affectation.