Je voudrais savoir quel sens vous mettez derriere le mot clé "const", ou
plutot quel contrat une fonction qui a des parametres const doit remplir.
Pour être plus clair, voici le cas qui m'interesse plus particulierement :
nous avons une classe "iterator" qui me permet d'itérer sur une structure de
donnée. Un "iterator" a de bonnes raisons (qui ne sont pas discutables :))
d'être un peu lourd à copier. Maintenant imaginons une fonction
"nbElementsAfter" qui fait ca :
int nbElementsAfter(iterator& it)
{
int nb = 0;
while(it.next()) nb++;
for(int i=0; i<nb; i++) it.prev();
return nb;
}
Est-ce que cette fonction doit prendre un iterator& ou un const iterator& ?
Lequel de ces contrats vous semble le plus sensé pour une fonction qui prend
un const iterator& : "je ne modifierai pas l'iterateur" ou "l'iterateur
pointera au même endroit avant et apres l'appel" (ou encore autre chose ?) ?
Comme tout exemple minimaliste, cette fonction est d'une utilité douteuse...
Je ne te le fais pas dire. En fait, j'avoue n'avoir jamais rencontré le problème dans une application réelle.
Mais il est concevable qu'en moyenne, le code ci-dessus soit plus rapide que le code suivant
Va savoir... Qu'est-ce qui prouve qu'une décrémentation sera plus rapide qu'une copie ?
Vincent Lascaux
Je voudrais dire "Je soussigné le programmeur m'engage à ce qu'apres l'execution de la fonction, la variable aura toujours la même valeur"
Je comprends l'idée, mais la formulation me paraît imprécise. Méfie-toi des mots comme "toujours" qui ont plusieurs sens légèrement différents.
Je veux dire "l'état de la variable après l'execution de la fonction est le même qu'avant l'execution de la fonction"
Le problème le plus immédiat : s'assurer que toutes les opérations "next()" sont réversibles en temps normal. [...]
Second problème : s'assurer que le contrat est bien respecté en cas d'exception. [...]
Ces deux problemes sont là aussi avec la version non const de nbElementsAfter puisqu'on s'attend là aussi à ce que l'itérateur ne soit pas déplacé (même si le compilateur ne le sait pas lui). Avoir une fonction qui ne remplit pas son contrat (ie un bug) c'est mal, mais bon, ca arrive, et je vois pas pourquoi on devrait s'inquieter plus sur ce bug que sur un autre (s'assurer que nbElementsAfter retourne bien la bonne valeur est aussi assez difficile). Pour la petite info, en mode debug, on utilise RAII pour prendre l'état de l'objet à l'entrée de la fonction et vérifier à la sortie que c'est le même.
Troisième problème : s'assurer que pendant l'exécution de la fonction, aucun code ne s'attend à voir la valeur de départ dans l'objet. Bien évidemment, le cas du multithread vient tout de suite à l'esprit
Je n'est pas à me soucier de probleme de multi threading pour le code sur lequel je travaille, et je ne suis pas un expert dans ce domaine, mais si c'était le cas, il est clair que nbElementsAfter devrait "locker" l'itérateur comme pour le modifier dans le bloc de la fonction.
(et j'ai appris à mes dépens que sous Windows au moins, on travaille parfois en multithread sans trop s'en rendre compte),
Tu peux donner quelques détails ? Je suis pas sur de comprendre...
mais il est possible d'imaginer des cas où le code même de next() (ou de prev()) s'attend à ce que l'objet passé en argument ait sa valeur de départ.
T'aurais un exemple ?
-- Vincent
Je voudrais dire "Je soussigné le programmeur m'engage à ce qu'apres
l'execution de la fonction, la variable aura toujours la même valeur"
Je comprends l'idée, mais la formulation me paraît imprécise.
Méfie-toi des mots comme "toujours" qui ont plusieurs sens légèrement
différents.
Je veux dire "l'état de la variable après l'execution de la fonction est le
même qu'avant l'execution de la fonction"
Le problème le plus immédiat : s'assurer que toutes les opérations
"next()" sont réversibles en temps normal. [...]
Second problème : s'assurer que le contrat est bien respecté
en cas d'exception. [...]
Ces deux problemes sont là aussi avec la version non const de
nbElementsAfter puisqu'on s'attend là aussi à ce que l'itérateur ne soit pas
déplacé (même si le compilateur ne le sait pas lui).
Avoir une fonction qui ne remplit pas son contrat (ie un bug) c'est mal,
mais bon, ca arrive, et je vois pas pourquoi on devrait s'inquieter plus sur
ce bug que sur un autre (s'assurer que nbElementsAfter retourne bien la
bonne valeur est aussi assez difficile).
Pour la petite info, en mode debug, on utilise RAII pour prendre l'état de
l'objet à l'entrée de la fonction et vérifier à la sortie que c'est le même.
Troisième problème : s'assurer que pendant l'exécution de la fonction,
aucun code ne s'attend à voir la valeur de départ dans l'objet.
Bien évidemment, le cas du multithread vient tout de suite à l'esprit
Je n'est pas à me soucier de probleme de multi threading pour le code sur
lequel je travaille, et je ne suis pas un expert dans ce domaine, mais si
c'était le cas, il est clair que nbElementsAfter devrait "locker"
l'itérateur comme pour le modifier dans le bloc de la fonction.
(et j'ai appris à mes dépens que sous Windows au moins, on travaille
parfois en multithread sans trop s'en rendre compte),
Tu peux donner quelques détails ? Je suis pas sur de comprendre...
mais il est
possible d'imaginer des cas où le code même de next() (ou de prev())
s'attend à ce que l'objet passé en argument ait sa valeur de départ.
Je voudrais dire "Je soussigné le programmeur m'engage à ce qu'apres l'execution de la fonction, la variable aura toujours la même valeur"
Je comprends l'idée, mais la formulation me paraît imprécise. Méfie-toi des mots comme "toujours" qui ont plusieurs sens légèrement différents.
Je veux dire "l'état de la variable après l'execution de la fonction est le même qu'avant l'execution de la fonction"
Le problème le plus immédiat : s'assurer que toutes les opérations "next()" sont réversibles en temps normal. [...]
Second problème : s'assurer que le contrat est bien respecté en cas d'exception. [...]
Ces deux problemes sont là aussi avec la version non const de nbElementsAfter puisqu'on s'attend là aussi à ce que l'itérateur ne soit pas déplacé (même si le compilateur ne le sait pas lui). Avoir une fonction qui ne remplit pas son contrat (ie un bug) c'est mal, mais bon, ca arrive, et je vois pas pourquoi on devrait s'inquieter plus sur ce bug que sur un autre (s'assurer que nbElementsAfter retourne bien la bonne valeur est aussi assez difficile). Pour la petite info, en mode debug, on utilise RAII pour prendre l'état de l'objet à l'entrée de la fonction et vérifier à la sortie que c'est le même.
Troisième problème : s'assurer que pendant l'exécution de la fonction, aucun code ne s'attend à voir la valeur de départ dans l'objet. Bien évidemment, le cas du multithread vient tout de suite à l'esprit
Je n'est pas à me soucier de probleme de multi threading pour le code sur lequel je travaille, et je ne suis pas un expert dans ce domaine, mais si c'était le cas, il est clair que nbElementsAfter devrait "locker" l'itérateur comme pour le modifier dans le bloc de la fonction.
(et j'ai appris à mes dépens que sous Windows au moins, on travaille parfois en multithread sans trop s'en rendre compte),
Tu peux donner quelques détails ? Je suis pas sur de comprendre...
mais il est possible d'imaginer des cas où le code même de next() (ou de prev()) s'attend à ce que l'objet passé en argument ait sa valeur de départ.
T'aurais un exemple ?
-- Vincent
Vincent Lascaux
Qu'est-ce qui prouve qu'une décrémentation sera plus rapide qu'une copie ?
Rien ne le prouve, mais j'ai en tête un operateur ++ qui fait quelque chose comme (sur une représentation en base Base)
int i = 0; while (i<val.size() && val[i] == Base-1) val[i++] = 0; if(i == val.size()) val.push_back(1); else val[i]++;
Si on prend un nombre aléatoire, la probabilité de modifier exactement n chiffres est (Base-1)/Base^n (si je ne m'abuse). La moyenne est donc (Base-1)*Sum(n/Base^n) Pour un nombre de 100 chiffres, avec Base = 256, on va modifier en moyenne moins de 1.004 élements, contre 100 pour une copie... Il y a forcément une taille de nombre à partir de laquelle mon opérateur ++ est plus rapide qu'une copie de l'intégralité du nombre.
-- Vincent
Qu'est-ce qui prouve qu'une décrémentation sera plus
rapide qu'une copie ?
Rien ne le prouve, mais j'ai en tête un operateur ++ qui fait quelque chose
comme (sur une représentation en base Base)
int i = 0;
while (i<val.size() && val[i] == Base-1)
val[i++] = 0;
if(i == val.size())
val.push_back(1);
else
val[i]++;
Si on prend un nombre aléatoire, la probabilité de modifier exactement n
chiffres est (Base-1)/Base^n (si je ne m'abuse). La moyenne est donc
(Base-1)*Sum(n/Base^n)
Pour un nombre de 100 chiffres, avec Base = 256, on va modifier en moyenne
moins de 1.004 élements, contre 100 pour une copie... Il y a forcément une
taille de nombre à partir de laquelle mon opérateur ++ est plus rapide
qu'une copie de l'intégralité du nombre.
Qu'est-ce qui prouve qu'une décrémentation sera plus rapide qu'une copie ?
Rien ne le prouve, mais j'ai en tête un operateur ++ qui fait quelque chose comme (sur une représentation en base Base)
int i = 0; while (i<val.size() && val[i] == Base-1) val[i++] = 0; if(i == val.size()) val.push_back(1); else val[i]++;
Si on prend un nombre aléatoire, la probabilité de modifier exactement n chiffres est (Base-1)/Base^n (si je ne m'abuse). La moyenne est donc (Base-1)*Sum(n/Base^n) Pour un nombre de 100 chiffres, avec Base = 256, on va modifier en moyenne moins de 1.004 élements, contre 100 pour une copie... Il y a forcément une taille de nombre à partir de laquelle mon opérateur ++ est plus rapide qu'une copie de l'intégralité du nombre.
-- Vincent
Fabien LE LEZ
On Sat, 7 Jan 2006 22:41:04 -0800, "Vincent Lascaux" :
Ces deux problemes sont là aussi avec la version non const de nbElementsAfter puisqu'on s'attend là aussi à ce que l'itérateur ne soit pas déplacé (même si le compilateur ne le sait pas lui).
Sans doute. Mais j'essayais d'expliquer pourquoi le langage ne contient rien pour indiquer que la valeur est la même à la fin et au début.
(et j'ai appris à mes dépens que sous Windows au moins, on travaille parfois en multithread sans trop s'en rendre compte),
Tu peux donner quelques détails ? Je suis pas sur de comprendre...
Par exemple, un code appelé en callback par un timer multimédia s'exécute dans un thread différent de celui qui a créé le timer.
On Sat, 7 Jan 2006 22:41:04 -0800, "Vincent Lascaux" :
Ces deux problemes sont là aussi avec la version non const de
nbElementsAfter puisqu'on s'attend là aussi à ce que l'itérateur ne soit pas
déplacé (même si le compilateur ne le sait pas lui).
Sans doute. Mais j'essayais d'expliquer pourquoi le langage ne
contient rien pour indiquer que la valeur est la même à la fin et au
début.
(et j'ai appris à mes dépens que sous Windows au moins, on travaille
parfois en multithread sans trop s'en rendre compte),
Tu peux donner quelques détails ? Je suis pas sur de comprendre...
Par exemple, un code appelé en callback par un timer multimédia
s'exécute dans un thread différent de celui qui a créé le timer.
On Sat, 7 Jan 2006 22:41:04 -0800, "Vincent Lascaux" :
Ces deux problemes sont là aussi avec la version non const de nbElementsAfter puisqu'on s'attend là aussi à ce que l'itérateur ne soit pas déplacé (même si le compilateur ne le sait pas lui).
Sans doute. Mais j'essayais d'expliquer pourquoi le langage ne contient rien pour indiquer que la valeur est la même à la fin et au début.
(et j'ai appris à mes dépens que sous Windows au moins, on travaille parfois en multithread sans trop s'en rendre compte),
Tu peux donner quelques détails ? Je suis pas sur de comprendre...
Par exemple, un code appelé en callback par un timer multimédia s'exécute dans un thread différent de celui qui a créé le timer.
Fabien LE LEZ
On Sat, 7 Jan 2006 22:41:04 -0800, "Vincent Lascaux" :
mais il est possible d'imaginer des cas où le code même de next() (ou de prev()) s'attend à ce que l'objet passé en argument ait sa valeur de départ.
T'aurais un exemple ?
J'avoue que je ne suis pas doué pour les exemples tordus. Je peux toutefois te proposer ça :
Imagine une structure de données sous la forme de deux tableaux (par exemple, deux std::vector<>). Un itérateur va parcourir le premier tableau, puis le deuxième.
Passons maintenant à la fonction TailleDeuxiemeTableau(). Celle-là est très simple : il suffit de calculer le nombre d'éléments entre le début du deuxième tableau et la fin. Une ligne suffit :
int TailleDeuxiemeTableau() const; { return nbElementsAfter (end_tableau_1); }
On Sat, 7 Jan 2006 22:41:04 -0800, "Vincent Lascaux" :
mais il est
possible d'imaginer des cas où le code même de next() (ou de prev())
s'attend à ce que l'objet passé en argument ait sa valeur de départ.
T'aurais un exemple ?
J'avoue que je ne suis pas doué pour les exemples tordus. Je peux
toutefois te proposer ça :
Imagine une structure de données sous la forme de deux tableaux (par
exemple, deux std::vector<>). Un itérateur va parcourir le premier
tableau, puis le deuxième.
Passons maintenant à la fonction TailleDeuxiemeTableau(). Celle-là est
très simple : il suffit de calculer le nombre d'éléments entre le
début du deuxième tableau et la fin. Une ligne suffit :
int TailleDeuxiemeTableau() const;
{
return nbElementsAfter (end_tableau_1);
}
On Sat, 7 Jan 2006 22:41:04 -0800, "Vincent Lascaux" :
mais il est possible d'imaginer des cas où le code même de next() (ou de prev()) s'attend à ce que l'objet passé en argument ait sa valeur de départ.
T'aurais un exemple ?
J'avoue que je ne suis pas doué pour les exemples tordus. Je peux toutefois te proposer ça :
Imagine une structure de données sous la forme de deux tableaux (par exemple, deux std::vector<>). Un itérateur va parcourir le premier tableau, puis le deuxième.
Passons maintenant à la fonction TailleDeuxiemeTableau(). Celle-là est très simple : il suffit de calculer le nombre d'éléments entre le début du deuxième tableau et la fin. Une ligne suffit :
int TailleDeuxiemeTableau() const; { return nbElementsAfter (end_tableau_1); }
Jean-Marc Bourguet
Fabien LE LEZ writes:
On Sat, 7 Jan 2006 20:10:45 -0800, "Vincent Lascaux" :
int nbElementsAfter(const iterator& const_it) { iterator& it = const_cast<iterator&>(const_it);
int nb = 0; while(it.next()) nb++;
C'est un comportement indéfini.
Il me semble que c'est un comportement indéfini uniquement dans le cas où l'iterateur passé a été déclaré const mais pas dans les autres cas.
Donc avec
iterator i;
ça passe, avec
iterator const i;
non.
Vu que le const en C++ est plus logique que physique (voir mutable par exemple), ça ne me gène pas trop tant qu'on est sur que l'itérateur ne sera *jamais* modifié, même en cas d'exceptions. Mais ce n'est pas pour autant que je le ferai.
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
Fabien LE LEZ <gramster@gramster.com> writes:
On Sat, 7 Jan 2006 20:10:45 -0800, "Vincent Lascaux"
<nospam@nospam.org>:
int nbElementsAfter(const iterator& const_it)
{
iterator& it = const_cast<iterator&>(const_it);
int nb = 0;
while(it.next()) nb++;
C'est un comportement indéfini.
Il me semble que c'est un comportement indéfini uniquement
dans le cas où l'iterateur passé a été déclaré const mais
pas dans les autres cas.
Donc avec
iterator i;
ça passe, avec
iterator const i;
non.
Vu que le const en C++ est plus logique que physique (voir
mutable par exemple), ça ne me gène pas trop tant qu'on est
sur que l'itérateur ne sera *jamais* modifié, même en cas
d'exceptions. Mais ce n'est pas pour autant que je le
ferai.
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
On Sat, 7 Jan 2006 20:10:45 -0800, "Vincent Lascaux" :
int nbElementsAfter(const iterator& const_it) { iterator& it = const_cast<iterator&>(const_it);
int nb = 0; while(it.next()) nb++;
C'est un comportement indéfini.
Il me semble que c'est un comportement indéfini uniquement dans le cas où l'iterateur passé a été déclaré const mais pas dans les autres cas.
Donc avec
iterator i;
ça passe, avec
iterator const i;
non.
Vu que le const en C++ est plus logique que physique (voir mutable par exemple), ça ne me gène pas trop tant qu'on est sur que l'itérateur ne sera *jamais* modifié, même en cas d'exceptions. Mais ce n'est pas pour autant que je le ferai.
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
kanze
Fabien LE LEZ wrote:
[...]
Au fait, si tu appelles ton objet "iterator", il vaudrait mieux lui mettre des opérateurs ++ et -- au lieu de next() et prev().
Ça se discute. L'utilisation de next() et de prev() est bien plus compréhensible.
-- James Kanze GABI Software 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
Fabien LE LEZ wrote:
[...]
Au fait, si tu appelles ton objet "iterator", il vaudrait
mieux lui mettre des opérateurs ++ et -- au lieu de next() et
prev().
Ça se discute. L'utilisation de next() et de prev() est bien
plus compréhensible.
--
James Kanze GABI Software
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
Au fait, si tu appelles ton objet "iterator", il vaudrait mieux lui mettre des opérateurs ++ et -- au lieu de next() et prev().
Ça se discute. L'utilisation de next() et de prev() est bien plus compréhensible.
-- James Kanze GABI Software 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
kanze
Fabien LE LEZ wrote:
On Sat, 7 Jan 2006 20:10:45 -0800, "Vincent Lascaux" :
int nbElementsAfter(const iterator& const_it) { iterator& it = const_cast<iterator&>(const_it);
int nb = 0; while(it.next()) nb++;
C'est un comportement indéfini.
Pas du tout. Ce n'est un comportement indéfini que si l'itérateur passé en paramètre est réelement const. Et définir des itérateurs qui sont const, ça ne doit pas se produire souvent.
Évidemment, ce n'est pas pour autant que c'est une bonne idée. Le const donne bien un mauvais message à l'utilisateur.
-- James Kanze GABI Software 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
Fabien LE LEZ wrote:
On Sat, 7 Jan 2006 20:10:45 -0800, "Vincent Lascaux"
<nospam@nospam.org>:
int nbElementsAfter(const iterator& const_it)
{
iterator& it = const_cast<iterator&>(const_it);
int nb = 0;
while(it.next()) nb++;
C'est un comportement indéfini.
Pas du tout. Ce n'est un comportement indéfini que si
l'itérateur passé en paramètre est réelement const. Et définir
des itérateurs qui sont const, ça ne doit pas se produire
souvent.
Évidemment, ce n'est pas pour autant que c'est une bonne idée.
Le const donne bien un mauvais message à l'utilisateur.
--
James Kanze GABI Software
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
On Sat, 7 Jan 2006 20:10:45 -0800, "Vincent Lascaux" :
int nbElementsAfter(const iterator& const_it) { iterator& it = const_cast<iterator&>(const_it);
int nb = 0; while(it.next()) nb++;
C'est un comportement indéfini.
Pas du tout. Ce n'est un comportement indéfini que si l'itérateur passé en paramètre est réelement const. Et définir des itérateurs qui sont const, ça ne doit pas se produire souvent.
Évidemment, ce n'est pas pour autant que c'est une bonne idée. Le const donne bien un mauvais message à l'utilisateur.
-- James Kanze GABI Software 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
kanze
Fabien LE LEZ wrote:
On Sat, 7 Jan 2006 21:11:04 -0800, "Vincent Lascaux" :
Quelle est la raison de cette interdiction de faire des const_cast ?
Parce que const_cast<> n'est pas un opérateur de conversion à proprement parler, mais un moyen de dire au compilateur "Je soussigné le programmeur, m'engage à ce que tel code ne modifie pas telle variable, même si toi, le compilateur, tu ne peux pas t'en rendre compte."
D'où est-ce que tu tiens cette information ? Selon la norme (§5.2.11/1), « Conversions that can be performed explicitly using const_cast are listed below. »
Et ce que tu dis au compilateur avec const_cast, ce n'est pas que tu ne vas pas modifier la variable, mais plutôt que l'objet auquel le pointeur ou la référence réfère n'est pas en fait const, bien que l'expression au départ contient un const. (Mais le compilateur est obligé de prendre en compte ce fait de tout façon en ce qui concerne les optimisations.)
Si tu romps cet engagement, i.e. si tu mens, le résultat est indéfini.
Si l'objet auquel le pointeur ou la référence est réelement const, et tu essaies de le modifier au moyen d'un const_cast, tu as un comportement indéfini. Sinon, il n'y a pas de problème.
C'est un peu la même chose pour reinterpret_cast<> : tu peux t'en servir pour dire "J'affirme que tel pointeur pointe en fait sur une variable de tel type.", mais si c'est un mensonge, le résultat est indéfini.
Exactement. De même avec const_cast, tu affirme que tel pointeur pointe en fait sur un objet qui n'est pas const.
Dans les deux cas, il y a des exceptions ; des cas qui sont garantis à marcher même si tu mens. Donc, au moyen de reinterpret_cast, tu peux régarder n'importe quel type comme si c'était un tableau de unsigned char, sans comportement indéfini. Et dans le cas de const_cast, tant que tu n'essaies pas de modifier un objet qui est réelement const, tu as un comportement on ne peut plus défini.
C'est pour laisser plus de liberté d'optimisation au compilateur ?
Il y a aussi de ça.
Sauf qu'il ne marche pas pour ça. Ce n'est pas parce qu'il y a une référence à const que le compilateur peut supposer que la valeur de l'objet ne change pas.
int main() { int const n= 42; int& ptr_n= const_cast<int>(n); ptr_n= 0; cout << n << endl; }
Le programme ci-dessus a un comportement indéfini : il peut afficher à peu près n'importe quoi. En pratique, il y a de fortes chances pour qu'il affiche 0 ou 42, suivant le compilo et les options de compilation.
Mais le problème ci-dessus, c'est que tu as essayé de modifier un objet const, non le fait du const_cast. Si, par exemple, tu écris :
int main() { int n = 42 ; int const& r1 = n ; int& r2 = const_cast< int& >( r1 ) ; r2 = 0 ; std::cout << n << std::endl ; return 0 ; }
Tu as un comportement bien défini, et tout compilateur conforme sortira 0.
-- James Kanze GABI Software 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
Fabien LE LEZ wrote:
On Sat, 7 Jan 2006 21:11:04 -0800, "Vincent Lascaux" :
Quelle est la raison de cette interdiction de faire des
const_cast ?
Parce que const_cast<> n'est pas un opérateur de conversion à
proprement parler, mais un moyen de dire au compilateur "Je
soussigné le programmeur, m'engage à ce que tel code ne
modifie pas telle variable, même si toi, le compilateur, tu ne
peux pas t'en rendre compte."
D'où est-ce que tu tiens cette information ? Selon la norme
(§5.2.11/1), « Conversions that can be performed explicitly
using const_cast are listed below. »
Et ce que tu dis au compilateur avec const_cast, ce n'est pas
que tu ne vas pas modifier la variable, mais plutôt que l'objet
auquel le pointeur ou la référence réfère n'est pas en fait
const, bien que l'expression au départ contient un const. (Mais
le compilateur est obligé de prendre en compte ce fait de tout
façon en ce qui concerne les optimisations.)
Si tu romps cet engagement, i.e. si tu mens, le résultat est
indéfini.
Si l'objet auquel le pointeur ou la référence est réelement
const, et tu essaies de le modifier au moyen d'un const_cast, tu
as un comportement indéfini. Sinon, il n'y a pas de problème.
C'est un peu la même chose pour reinterpret_cast<> : tu peux
t'en servir pour dire "J'affirme que tel pointeur pointe en
fait sur une variable de tel type.", mais si c'est un
mensonge, le résultat est indéfini.
Exactement. De même avec const_cast, tu affirme que tel pointeur
pointe en fait sur un objet qui n'est pas const.
Dans les deux cas, il y a des exceptions ; des cas qui sont
garantis à marcher même si tu mens. Donc, au moyen de
reinterpret_cast, tu peux régarder n'importe quel type comme si
c'était un tableau de unsigned char, sans comportement indéfini.
Et dans le cas de const_cast, tant que tu n'essaies pas de
modifier un objet qui est réelement const, tu as un comportement
on ne peut plus défini.
C'est pour laisser plus de liberté d'optimisation au
compilateur ?
Il y a aussi de ça.
Sauf qu'il ne marche pas pour ça. Ce n'est pas parce qu'il y a
une référence à const que le compilateur peut supposer que la
valeur de l'objet ne change pas.
int main()
{
int const n= 42;
int& ptr_n= const_cast<int>(n);
ptr_n= 0;
cout << n << endl;
}
Le programme ci-dessus a un comportement indéfini : il peut
afficher à peu près n'importe quoi. En pratique, il y a de
fortes chances pour qu'il affiche 0 ou 42, suivant le compilo
et les options de compilation.
Mais le problème ci-dessus, c'est que tu as essayé de modifier
un objet const, non le fait du const_cast. Si, par exemple, tu
écris :
int
main()
{
int n = 42 ;
int const& r1 = n ;
int& r2 = const_cast< int& >( r1 ) ;
r2 = 0 ;
std::cout << n << std::endl ;
return 0 ;
}
Tu as un comportement bien défini, et tout compilateur conforme
sortira 0.
--
James Kanze GABI Software
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
On Sat, 7 Jan 2006 21:11:04 -0800, "Vincent Lascaux" :
Quelle est la raison de cette interdiction de faire des const_cast ?
Parce que const_cast<> n'est pas un opérateur de conversion à proprement parler, mais un moyen de dire au compilateur "Je soussigné le programmeur, m'engage à ce que tel code ne modifie pas telle variable, même si toi, le compilateur, tu ne peux pas t'en rendre compte."
D'où est-ce que tu tiens cette information ? Selon la norme (§5.2.11/1), « Conversions that can be performed explicitly using const_cast are listed below. »
Et ce que tu dis au compilateur avec const_cast, ce n'est pas que tu ne vas pas modifier la variable, mais plutôt que l'objet auquel le pointeur ou la référence réfère n'est pas en fait const, bien que l'expression au départ contient un const. (Mais le compilateur est obligé de prendre en compte ce fait de tout façon en ce qui concerne les optimisations.)
Si tu romps cet engagement, i.e. si tu mens, le résultat est indéfini.
Si l'objet auquel le pointeur ou la référence est réelement const, et tu essaies de le modifier au moyen d'un const_cast, tu as un comportement indéfini. Sinon, il n'y a pas de problème.
C'est un peu la même chose pour reinterpret_cast<> : tu peux t'en servir pour dire "J'affirme que tel pointeur pointe en fait sur une variable de tel type.", mais si c'est un mensonge, le résultat est indéfini.
Exactement. De même avec const_cast, tu affirme que tel pointeur pointe en fait sur un objet qui n'est pas const.
Dans les deux cas, il y a des exceptions ; des cas qui sont garantis à marcher même si tu mens. Donc, au moyen de reinterpret_cast, tu peux régarder n'importe quel type comme si c'était un tableau de unsigned char, sans comportement indéfini. Et dans le cas de const_cast, tant que tu n'essaies pas de modifier un objet qui est réelement const, tu as un comportement on ne peut plus défini.
C'est pour laisser plus de liberté d'optimisation au compilateur ?
Il y a aussi de ça.
Sauf qu'il ne marche pas pour ça. Ce n'est pas parce qu'il y a une référence à const que le compilateur peut supposer que la valeur de l'objet ne change pas.
int main() { int const n= 42; int& ptr_n= const_cast<int>(n); ptr_n= 0; cout << n << endl; }
Le programme ci-dessus a un comportement indéfini : il peut afficher à peu près n'importe quoi. En pratique, il y a de fortes chances pour qu'il affiche 0 ou 42, suivant le compilo et les options de compilation.
Mais le problème ci-dessus, c'est que tu as essayé de modifier un objet const, non le fait du const_cast. Si, par exemple, tu écris :
int main() { int n = 42 ; int const& r1 = n ; int& r2 = const_cast< int& >( r1 ) ; r2 = 0 ; std::cout << n << std::endl ; return 0 ; }
Tu as un comportement bien défini, et tout compilateur conforme sortira 0.
-- James Kanze GABI Software 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
kanze
Vincent Lascaux wrote:
Note que tu pars d'emblée sur de mauvaises bases : un itérateur au sens de la STL ("iterator") est un objet très souvent copié. On ne le passe quasiment jamais par référence const.
Et qui dit que je fais un "iterator" au sens de la STL ? J'aurais du donner un autre nom à cette classe :)
Dans mon propre code, je suis la convention que les noms de types commencent toujours par une majuscule. Du coup, j'ai des Iterator, qui sont des itérateurs, et des « iterator », qui sont les batards à la STL. (En fait, j'essaie assez souvent à supporter les deux interfaces.)
-- James Kanze GABI Software 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
Vincent Lascaux wrote:
Note que tu pars d'emblée sur de mauvaises bases : un
itérateur au sens de la STL ("iterator") est un objet très
souvent copié. On ne le passe quasiment jamais par référence
const.
Et qui dit que je fais un "iterator" au sens de la STL ?
J'aurais du donner un autre nom à cette classe :)
Dans mon propre code, je suis la convention que les noms de
types commencent toujours par une majuscule. Du coup, j'ai des
Iterator, qui sont des itérateurs, et des « iterator », qui sont
les batards à la STL. (En fait, j'essaie assez souvent à
supporter les deux interfaces.)
--
James Kanze GABI Software
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
Note que tu pars d'emblée sur de mauvaises bases : un itérateur au sens de la STL ("iterator") est un objet très souvent copié. On ne le passe quasiment jamais par référence const.
Et qui dit que je fais un "iterator" au sens de la STL ? J'aurais du donner un autre nom à cette classe :)
Dans mon propre code, je suis la convention que les noms de types commencent toujours par une majuscule. Du coup, j'ai des Iterator, qui sont des itérateurs, et des « iterator », qui sont les batards à la STL. (En fait, j'essaie assez souvent à supporter les deux interfaces.)
-- James Kanze GABI Software 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