Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Un doute existenciel a propos du parcours de liste dans une boucle 'for'

79 réponses
Avatar
meow
> for ( iterator it=3Dliste.begin() ; it!=3Dliste.end() ; ++it )

Question =E0 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=E9but
de la boucle, du coup =E0 chaque it=E9ration de la boucle le programme se
retape l'appel =E0 liste.end() !
La question toute simple que je me pose est donc : est-ce que c'est pas
mieux de faire

> iterator end=3Dliste.end()
> for ( iterator it=3Dliste.begin() ; it!=3Dend ; ++it )

10 réponses

Avatar
Sylvain
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ées,
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.

Sylvain.


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



Tiens, j'ai un collègue qui a introduit un bug comme ça récemment. Bon,
c'était du Java et le code dans la boucle ferait un malheur à n'importe
quel concours d'obfuscation.

Sinon, j'aime pas mettre des trucs dans la portée englobante comme ta
variable end, ici, alors qu'elles ne sont de sens que dans la boucle.

Pour l'optimisation, théoriquement, cela ne devrait avoir d'impact que
si le contenu de la boucle n'est lui-même pas beaucoup plus lourd qu'un
simple appel à end(). Or dans ce cas souvent, je dis bien "souvent",
c'est que le code est assez simple pour que le compilateur optimise ça
tout seul comme un grand.

--
Les lois sont toujours utiles à ceux qui possèdent et nuisibles à ceux
qui n'ont rien. ~ Rousseau, du Contrat Social.


Avatar
Jean-Marc Bourguet
Sylvain writes:

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,


Si le test est nécessaire pour éviter de perdre des données dans le cas de
l'auto-affectation, il y a de gros risques que le code en fait ne soit pas
robustes en présence d'exceptions.

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


Pourquoi pas tout simplement:

if (other.ref) other.ref->addRef();
if (ref) ref->release();
ref = other.ref;

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
James Kanze
Sylvain wrote:
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,


Un exemple, stp. Je ne vois pas de tête un cas où l'opérateur
d'affectation marcherait dans le cas de l'auto-affectation, et
ne marcherait pas dans le cas de l'auto-affectation. C'est,
d'ailleurs, la réalisation de ce fait, suite à des discussions
avec David Abraham, entre d'autres, qui m'a fait conclure que la
nécessité d'un test pour l'auto-affectation est un symptome d'un
opérateur d'affectation incorrect, et qui a mené à ce que dit
Herb.

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.


Et pourquoi est-ce qu'il y a tous ces autres tests ?

On est d'accord qu'avec le test pour auto-affectation,
l'opérateur d'affectation est plus rapide dans le cas de
l'auto-affectation. Seulement, il est aussi moins rapide (de
peu, j'en conviens) dans les autres cas. Or, je peux dire qu'au
moins dans mon code, les cas d'auto-affectation sont
l'exception ; la plupart des affectations, de loin, ne sont pas
des auto-affectations. L'effet global du test est, alors, de
rallentir le code (mais encore, de très, très peu).

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?


Ça dépend des données:-). Et du contexte -- j'écris souvent des
opérateurs simplement pour qu'ils ne soit pas inline.

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,


Tu crois ? Je doute qu'il y a un test d'auto-affectation dans
std::basic_string ni dans std::vector. L'implémentation
« canonique » de l'opérateur d'affectation pour de tels types
de classe, c'est :

Container&
Container::operator=( Container const& other )
{
Container tmp( other ) ;
swap( tmp ) ;
return *this ;
}

(Il y a beaucoup de variants : on passe le paramètre par
valeur, pour éviter la copie dans le cas où la côté gauche de
l'affectation est un temporaire, certains préfèrent une seule
expression -- Container( other ).swap( *this ) -- à la place
de la variable nomée, etc.)

L'importance dans cette idiome, c'est évidemment, que la
fonction swap ne fait que d'échanger quelques pointeurs ou des
types de base, et que donc, elle ne peut pas lever une
exception.

(Toutes ces idées sort des discussions entre David Abraham, Herb
Sutter et moi. Où il faut dire que les idées venaient prèsque
toutes de David -- Herb et moi jouaient plutôt la rôle de
panneau de résonance.)

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.


Tout à fait.

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 pourquoi pas simplement :

if ( other.ref != NULL ) other.ref->addRef() ;
if ( ref != NULL ) ref->release() ;
ref = other.ref ;

(C'est ce que j'ai fait dans mes pointeurs à comptage de
référence. Bien avant les discussions avec David et Herb. Note
bien aussi que l'idiome canonique marche très bien ici.)

Ça a l'avantage de marcher même si addRef lève une exception
(disons parce qu'il y aurait débordement sinon, bien que moi,
j'avorte dans ce cas-là).

Plus généralement, l'idiome que tu proposes ici, c'est (grosso
modo) :

défaire l'object actuelle.
copier l'autre.

Dans un cas simple comme ci-dessus, ça pourrait marcher, à part
le problème de l'auto-affectation. Dans un cas plus complexe, il
faut compter avec l'idée que la copie pourrait lever une
exception -- remplacer le comptage de références par une copie
profonde dans ton exemple, par exemple. Alors, la règle de base,
c'est d'abord de faire la copie (c-à-d surtout obtenir toutes
nouvelles ressources que ça pourrait impliquer), puis défaire
l'objet actuelle, et de le remplacer par le résultat de la
copie. Et quand la « copie » se résume à l'incrémentation d'un
compteur, comme dans ton exemple ; on acquiert les nouvelles
ressources avant de libérer les anciennes. (En générale, on
suppose qu'une libération ne peut pas lever d'exception.)

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 préfère ma variant, qui est plus court, et plus conforme à
l'idiome général qui marche dans tous les cas, et non seulement
parce qu'exceptionnellement, on peut acquerir la ressource sans
risque d'exception.

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


On commence toujours par dupliquer ce qu'il faut dupliquer,
parce que la duplication peut souvent lever une exception (ne
serait-ce que bad_alloc). Seulement après la duplication a
réussie, on libère ce qu'il faut libérer, et on installe ce
qu'on a dupliqué. C'est un modèle qui marche à tout les coups,
et qui n'exige pas de test pour l'auto-affectation. Et si tu lis
ce qu'à dit Herb, la raison qu'il est contre le test
d'auto-affectation, c'est parce qu'il dit qu'il faut toujours
utiliser ce modèle, où le test est supérflu. Et c'est David qui
a rémarqué que l'utilisation du constructeur de copie, suivi de
swap, faisait exactement ça. Le constructeur de copie duplique,
avec allocation des nouvelles ressources nécessaire, swap
échange, de façon à ce que les anciennes ressources
appartiennent au temporaire, et les nouvelles à l'objet, et le
destructeur du temporaire ou de la variable locale libère les
ressources qui lui ont été transférées. Avec l'avantage en plus
que dans le cas d'une classe compliquée, on ne duplique pas la
logique de copie - on réutilise le constructeur de copie dans
l'opérateur d'affectation. Et que c'est 100% garanti, même suite
à des évolutions dans la classe, que la copie et l'affectation
ont la même sémantique.

Dans mon propre code, j'utilise l'idiome de swap dès que la
classe a plus d'un membre. Pour des cas classiques, genre
par-feu de compilation (idiome pimpl, bien que je déteste ce
nom), je continue à faire comme avant, c-à-d :

MaClasse&
MaClasse::operator=( MaClasse const& other )
{
Impl* tmp( new Impl( *other.impl ) ) ;
delete myImpl ;
myImpl = tmp ;
return *this ;
}

(Mais je passerais à boost::scoped_ptr dès que l'utilisation de
Boost devient universelle dans mon code. Et l'implémentation de
l'opérateur ne comportera plus qu'une seule instruction :
myImpl = new Impl( *other.impl ) ;
.)

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.


L'absence totale des tests d'auto-affectation me paraît valide
dès que j'alloue toutes les nouvelles ressources avant de
libérer les anciennes. Chose qu'il faut faire de toute façon, si
on ne veut pas de problèmes vis-à-vis des exceptions.

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
James Kanze
Alain Gaillard wrote:

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.


Tout à fait. Dans les deux cas, je vais avec la solution la plus
simple, jusqu'au preuve qu'il faut autre chose. Pas de test
supplémentaire, pas de variable supplémentaire.

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
James Kanze
Alain Gaillard wrote:

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.


Mais pourquoi est-ce que je libérer deux foix une même
ressource ?

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.


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 ;
}

Ce n'est pas la peine de dupliquer le code du constructeur de
copie.

Sinon faut tout lire en ayant le cas particulier de
l'auto-affectation en tête. Enfin ce n'est que mon avis :)


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.

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
James Kanze
Alain Gaillard wrote:

[...]
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.


Moi, un peu, mais ici, ce n'est pas la question. Les deux sont
équivalent en ce qui nous concerne ici.

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


L'idée est en fait de David Abraham (et je crois que Sutter lui
en donne crédit).

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


J'accepte facilement que dans les très rares cas où on définit
l'affectation, et non la copie, l'opérateur d'affectation risque
d'être un peu différent des idiomes consacrés:-). Et en passant,
je note que cet idiome ne vaut que si l'opérateur d'affectation
et le constructeur de copie doivent avoir la même sémantique.
C'est aussi généralement le cas, mais j'imagine qu'il peut y
avoir des exceptions, même si je ne les ai jamais rencontré.

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;
}


Intéressant. Tu prétends que l'opérateur d'affectation peut
lever une exception dans le cas général, mais qu'il garantit de
ne pas le faire dans le cas d'une auto-affectation. Je suis
curieux : est-ce que tu as un exemple où ce genre de garantie
sert ?

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
James Kanze
Alain Gaillard wrote:

[...]
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.


Je n'utilise pas les tableaux en Java, pas plus qu'en C++.

Et la question des perfs avec les conteneurs,


Tu parles des perfs, et tu utilises Java ? :-)

et les transtypages permanents que ça implique.


Il parait que le problème de transtypage a été résolu, et que
Java a aussi des templates aujourd'hui. Mais c'était
effectivement un problème dans le temps.

Bouh... revenons au bon vieux C++ :)


Si tu veux un typage statique, il vaut mieux.

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
James Kanze
Fabien LE LEZ wrote:
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 )


Ça m'est un peu égal. Je le préfère parce que conceptuellement,
je ne fais qu'une chose : itérer. Et que c'est donc une
aberration et une abomination d'avoir besoin de deux objets pour
le faire. Ta version a l'avantage, au moins, de n'avoir qu'une
seule définition.

Mais mon point sur l'utilisation des idiomes consacrés reste.
L'idiome standard aujourd'hui, c'est bien :

for ( iterator it = container.begin() ;
it != container.end() ;
++ it )

Si le code fait quoique ce soit d'autre, il doit y avoir une
raison, et je vais la chercher. S'écarter d'un idiome standard
sans motif, c'est d'introduire du bruit pour rien, et donc de
reduire le rapport signal/bruit. Ou en termes courants,
l'obfuscation.

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Alain Gaillard


Mais pourquoi est-ce que je libérer deux foix une même
ressource ?


Je ne me suis pas bien exprimé hier. Il était atrd et j'ai eu une dure
journée. Ce que je voulais dire c'ets que si tu as
a = a

Si, dans l'opérateur d'affectation, tu libères une ressource détenu par
a, elle est libérée des deux côtés du signal égal.


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.


Ah on! ce n'est pas ce que j'ai dit. J'ai dit je suis sûr qu'il n'y a
pas de problème dans le cas a = a

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 ?


On est dans le m...., je suis absolumenbt d'accord et je n'ai jamais dit
le contraire


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 ;
}



OUI !! Je suis d'accord

Et du coup, le test de l'auto-affectation devient superflu.


Non, je ne trouve pas.

MemberClass* tmp = new ( MemberClass( *other.myPtr ) ) ;

Peut lever une exception s'il n'y a pas le test. Je trouve que lever une
exception dans le cas de l'auto-affectation est idiot. De même
qu'exécuter plein de code. Donc je mets le test. Mais oui, je suis
d'accord, mettre le test ne veut pas dire qu'il n'y aura pas de probléme
derrière pour une affectation "normale" a = b. Je dis seulement qu'il
n'y a aucun problème dans le cas a = a.


(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 :


Qu'est-ce que vous avez tous à vous focalsier sur le fait que swap ne
lève pas d'exception ..; ->


MaClasse&
MaClasse::operator( MaClasse const& other )
{
MaClasse tmp( other ) ;
swap( tmp ) ;
return *this ;
}



... -> pourquoi MaClasse tmp( other ) ; ne lèverait-il pas une exception
lui ?


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.


Ben non c'ets la que je ne suis pas d'accord. Si un

MaClasse tmp( other ) ;

l'auto-affectation peut échouer. Bien sûr les objets restent en l'état,
c'est la force des idiomes consacrés comme tu dis. mais une exeption est
levée. Pour moi dans le cas de l'auto-affectation, je préfère ne rien
faire, d'où le test.


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.


Ah mais ça je suis bien d'accord.

--
Alain