OVH Cloud OVH Cloud

const_cast

22 réponses
Avatar
meow
J'ai donc commenc=E9 =E0 =E9tudier la question des casts, et je commence
par le const_cast.

1=2E) J'ai lu que cet 'op=E9rateur' ne fonctionnait que sur les types :
pointeurs, r=E9f=E9rences, et pointeurs sur membre...
a.) c'est quoi un pointeur sur membre
b.) pourquoi a t'on d=E9cid=E9 que =E7a fonctionnerait pas sur des
types tout betes ? o=F9 est le probl=E8me dans l'ecriture const_cast<A> ?

2=2E) J'ai ensuite test=E9 sur un exemple :

class A {
public:
void f(){std::cout<<"A::f()"<<std::endl;};
A(){;}; // (*)
};

int main()
{
const A a;
const_cast<A&>(a).f();
}

a.) la notation const_cast<A&> pour 'moralement' signifier
const_cast<A> me trouble un peu... y'a un truc que je vois pas ?
b.) si je commente la ligne marqu=E9e d'une at=E9risque (*), =E7a ne
compile pas : g++ pr=E9tend que je n'initialise pas mon "const A a;"...
Y'a pas de constructeur (vide) par d=E9faut ? M'aurait-t'on menti ?

10 réponses

1 2 3
Avatar
meow
Je complètes le mail précédent avec un autre comportement que je ne
comprends pas
3.) encore un exemple... Avec un comportent bizarre
void f1() {
int j=2; // 1
const int i=j; // 2
std::cout<<i<<" "; // 3
const_cast<int&>(i)=3; // 4
std::cout<<i<<std::endl; // 5
}
int main(void){f1();return 0;};

en sortie j'ai bien du "2 3" comme je m'y attend.
Par contre si je commentes la ligne 1, et que dans la ligne 2
j'initialise i avec "2" au lieu de "j"... Bein là mon programme me
sort "2 2" 0_o
C'est normal ?
Avatar
Fabien LE LEZ
On 7 Dec 2005 01:42:01 -0800, "meow" :

b.) pourquoi a t'on décidé que ça fonctionnerait pas sur des
types tout betes ? où est le problème dans l'ecriture const_cast<A> ?


Si tu as un pointeur "A const*", ça veut dire que l'objet pointé ne
peut pas être modifié.
Si tu veux pouvoir le modifier quand même, tu dois le transformer en
un "A*", avec const_cast<>.

Si tu as un objet "A const", et que tu veux un objet "A" non-const, tu
vas faire une copie, et appeler le constructeur "A::A (A const&)".
Pas besoin ici de const_cast<>, puisque l'objet original (const) ne
sera pas modifié -- le A créé est une copie.

const A a;
const_cast<A&>(a).f();

a.) la notation const_cast<A&> pour 'moralement' signifier
const_cast<A> me trouble un peu...


Il ne s'agit pas de ça. Tu veux appeler une fonction sur a, et pas une
copie. Donc, tu dois prendre une référence sur a (ou un pointeur),
modifier ("hacker") cette référence en une référence sur un objet
non-const, et enfin appeler f().

const A a;
static_cast<A>(a).f(); ---> Appelle f() sur une copie de a.

y'a un truc que je vois pas ?
b.) si je commente la ligne marquée d'une atérisque (*), ça ne
compile pas : g++ prétend que je n'initialise pas mon "const A a;"...
Y'a pas de constructeur (vide) par défaut ? M'aurait-t'on menti ?


C'est louche, ça. Essaie en enlevant les ";" parasites à la fin de tes
lignes :

class A {
public:
void f(){std::cout<<"A::f()"<<std::endl;}
};

Avatar
Fabien LE LEZ
On 7 Dec 2005 02:11:33 -0800, "meow" :

void f1() {
int j=2; // 1
const int i=j; // 2
std::cout<<i<<" "; // 3
const_cast<int&>(i)=3; // 4


Ceci est un abus pur et simple.

Dans le cas précédent, A::f() était à tort une fonction non-const,
mais qui ne modifie pas l'objet.
Le fait qu'elle n'ait pas été déclarée const alors qu'elle aurait dû,
t'autorise à utiliser const_cast<> : toi tu sais que A::f() ne modifie
pas l'objet, mais le compilo ne le sait pas, donc tu le lui dis.

Ici, tu modifies effectivement un objet const, ce que tu n'as pas le
droit de faire.

Par contre si je commentes la ligne 1, et que dans la ligne 2
j'initialise i avec "2" au lieu de "j"... Bein là mon programme me
sort "2 2" 0_o
C'est normal ?


Oui.
Si tu écris :

const int i= 2;

le compilateur a le droit de ne pas autoriser les modifications de la
variable.
En fait, comme tu ne t'occupes pas de l'adresse de cette variable i,
le compilo a le droit de considérer qu'il s'agit d'une constante, et
de remplacer "i" par "2" partout où c'est possible -- y compris dans
ta ligne (5).

Avatar
Fabien LE LEZ
On Wed, 07 Dec 2005 14:48:43 +0100, Fabien LE LEZ
:

Si tu veux pouvoir le modifier quand même, tu dois le transformer en
un "A*", avec const_cast<>.


En fait, tu ne peux le modifier que s'il était non-const au départ.

Mais const_cast<> sert surtout à appeler des fonctions déclarées
non-const à tort.

Avatar
Fabien LE LEZ
On Wed, 07 Dec 2005 14:48:43 +0100, Fabien LE LEZ
:

Si tu veux pouvoir le modifier quand même, tu dois le transformer en
un "A*", avec const_cast<>.


En fait, const_cast<> sert à appeler des fonctions déclarées non-const
à tort.

Avatar
kanze
meow wrote:
Je complètes le mail précédent avec un autre comportement que
je ne comprends pas

3.) encore un exemple... Avec un comportent bizarre
void f1() {
int j=2; // 1
const int i=j; // 2
std::cout<<i<<" "; // 3
const_cast<int&>(i)=3; // 4
std::cout<<i<<std::endl; // 5
}
int main(void){f1();return 0;};

en sortie j'ai bien du "2 3" comme je m'y attend. Par contre
si je commentes la ligne 1, et que dans la ligne 2
j'initialise i avec "2" au lieu de "j"... Bein là mon
programme me sort "2 2" 0_o

C'est normal ?


Pour quelle définition de normal ? Ton code n'est pas normal. Tu
as menti au compilateur ; il prend sa revanche.

En fait, essayer de modifier un objet défini const a un
comportement indéfini. C-à-d que quoique fasse le compilateur,
il a raison. Dans ce cas-ci, il est probable que le compilateur
note que tu as initialisé i avec une constante, et utilise
simplement la constante, sans jamais instancier i. Tandis que
quand tu l'initialise avec une variable (non-const), il ne sait
pas quelle valeur cette variable va prendre lors de l'exécution.
C'est d'ailleurs fort possible que dans des cas simple comme
ceci que le résultat dépend du niveau de l'optimisation.
Augmenter le niveau d'optimisation, et le compilateur va savoir
que j égal 2 lors de l'initialisation, et le traiter comme une
constante.

Note bien que ce n'est le cas que si l'objet même est const.
Modifier un objet non-const à travers un pointeur à const ou une
référence à const a un comportement tout à fait défini, même si
ce n'est pas souvent bon style. Il y a aussi une exception en ce
qui concerne les objets const -- si l'objet a un type classe, et
contient au moins un membre mutable, on peut le modifier
légalement, même s'il est défini const.

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

Avatar
meow
@kanze
Tu as menti au compilateur ; il prend sa revanche.
Je n'ai pas voulu cela ! :D

Au passage j'esquisse une réponse à ma question 1.b : les objets
consts peuvent faire l'objet d'optimisations du compilo... On ne peut
donc pas les modifier simplement. Par contre, les pointeurs et autres
références adressent toujours une adresse mémoire où on est certain
de trouver l'objet, et donc de pouvoir le modifier. Le langage permet
donc ce genre de modifs pour peu que le codeur précise qu'il sait ce
qu'il fait : en "déconstant"

@Fabien
Je ne connaissais pas la notation "A const *p"... ça a l'air d'etre
pareil que "const A *p"...

Il ne s'agit pas de ça. Tu veux appeler une fonction sur a, et pas une
copie. Donc, tu dois prendre une référence sur a (ou un pointeur),
modifier ("hacker") cette référence en une référence sur un objet
non-const, et enfin appeler f().
Compris


const A a;
static_cast<A>(a).f(); ---> Appelle f() sur une copie de a.
'O_o ...OK (dieu que l'apprentissage va etre long :( )


En fait, const_cast<> sert à appeler des fonctions déclarées non-co nst
à tort.
Si je comprends bien, on ne devrait jamais avoir à tuliser ce type de

casts...

Avatar
Fabien LE LEZ
On 8 Dec 2005 02:09:11 -0800, "meow" :

@Fabien
Je ne connaissais pas la notation "A const *p"... ça a l'air d'etre
pareil que "const A *p"...


Yep. L'idée est de mettre systématiquement "const" après ce qui est
constant.

A const * ptr : c'est l'objet de classe A qui est constant.
A * const ptr : c'est le pointeur qui est constant, pas l'objet
pointé.

En fait, const_cast<> sert à appeler des fonctions déclarées non-const
à tort.
Si je comprends bien, on ne devrait jamais avoir à tuliser ce type de

casts...


Exact. Mais parfois, les bibliothèques (surtout écrites en C) sont mal
faites, et const_cast<> sert à faire l'interface.


Avatar
loic.actarus.joly
Il y a aussi une exception en ce
qui concerne les objets const -- si l'objet a un type classe, et
contient au moins un membre mutable, on peut le modifier
légalement, même s'il est défini const.



Tiens, je saais pas ça. Et j'arrive pas à trouver où c'est défini
dans la norme. Tout ce que je vois, c'est :
Except that any class member declared mutable (7.1.1) can be modified, an y attempt to modify a const
object during its lifetime (3.8) results in undefined behavior.



Je vois qu'on peut modifier le membre mutable, mais rien du reste de
l'objet.

--
Loïc

Avatar
kanze
meow wrote:
@kanze

Tu as menti au compilateur ; il prend sa revanche.


Je n'ai pas voulu cela ! :D

Au passage j'esquisse une réponse à ma question 1.b : les
objets consts peuvent faire l'objet d'optimisations du
compilo...


Voire même se trouver dans la mémoire protégée en écriture. Si
j'écris « int const a = 32 ; », sur mon système, à la portée où
il a une durée de vie statique, et j'essaie de le modifier, au
coup de const_cast, j'ai un core dump.

On ne peut donc pas les modifier simplement. Par contre, les
pointeurs et autres références adressent toujours une adresse
mémoire où on est certain de trouver l'objet, et donc de
pouvoir le modifier.


Je ne comprends pas ce que tu essaies de dire.

Il y a deux notions plus ou moins distinctes en C++, celle d'un
objet const, et celle d'une expression lvalue const. Le
compilateur a le droit de mettre un *objet* const dans de la
mémoire protégée en écriture, et de supposer que sa valeur ne
change jamais, une fois qu'il a était initialisé. Et un
programme qui essaie à le modifier, quelque soit le moyen, a un
comportement indéfini. (Il y a une exception -- si l'objet a un
type classe, et comporte un membre mutable, l'objet même n'est
jamais const. Un const sur sa declaration ne joue que sur le
type de l'expression qui est son nom.)

Une expression lvalue const, en revanche, ne permet pas de
modifier la valeur à travers cette expression. Un essai de
modification à travers une telle expression rend le programme
« ill formed », c-à-d que le compilateur doit râler. En
revanche, on peut toujours appliquer un const_cast à cette
expression, ce qui donne une nouvelle expression, qui lui n'est
pas forcément const. Familièrement, on peut dire que tu utilises
const_cast pour dire que l'objet en question n'est pas const,
même si l'expression l'est. Et si dans ce cas, l'objet est
const, tu as menti au compilateur.

Le langage permet donc ce genre de modifs pour peu que le
codeur précise qu'il sait ce qu'il fait : en "déconstant"


Le langage dit qu'on n'a pas le droit de modifier un objet
const. Tu ne peux pas « déconster » l'objet, seulement
l'expression lvalue.

@Fabien
Je ne connaissais pas la notation "A const *p"... ça a l'air
d'etre pareil que "const A *p"...


En général, le const suit ce qui est constante, c-à-d :

A const* p ; // C'est le A qui est const
A *const p ; // C'est le pointeur qui est const

Mais aussi (dans une classe) :
A f() const ; // C'est la fonction qui est const
(Mais dire qu'une fonction est const a une signification un peu
différente que dire qu'un objet est const.)

Exceptionnellement, dans certains cas où aucune ambiguïté n'est
possible, il est aussi permis de mettre le const avant, par
exemple :
const A* p ;
Mais c'est une exception (qui en dérive naturellement des
idiosyncraties de la syntaxe de déclaration).

Historiquement, on a commencé en mettant le const avant, parce
qu'on ne pensait réelement qu'aux objets (au sens fort) const,
et pas à une utilisation systèmatique de const partout où il
pouvait s'appliquer. (Je crois, au moins. Si Gaby en a le temps,
il pourrait démander à Bjarne, parce que j'avoue que dans ce
cas-ci, ma croyance est plutôt de la spéculation.)

Quand const a été adopté par le comité C, c'était surtout en vue
de pouvoir définir des objets const, que le compilateur pouvait
mettre en ROM. Ce qui faisait qu'on ne l'appliquait pas
tellement au pointeurs non plus, et qu'on a adopté l'habitude de
le mettre avant. (Mais le C a repris le const de C++. Ici non
plus, je ne sais pas si c'était la motivation originale en C++.)

Aujourd'hui, qu'on a une véritable expérience avec const, et
qu'on a réelement expérimenté les avantages qu'il peut apporter,
on préfère l'utiliser partout où il pourrait s'appliquer. Du
coup, il est obligé d'apparaître derrière dans beaucoup de cas,
et par souci d'orthogonalité, la plupart des experts (je
crois -- ici non plus, je n'ai pas réelement fait un sondage)
préfère le mettre systèmatiquement derrière.

Il ne s'agit pas de ça. Tu veux appeler une fonction sur a,
et pas une copie. Donc, tu dois prendre une référence sur a
(ou un pointeur), modifier ("hacker") cette référence en une
référence sur un objet non-const, et enfin appeler f().


Compris

const A a;
static_cast<A>(a).f(); ---> Appelle f() sur une copie de a.
'O_o ...OK (dieu que l'apprentissage va etre long :( )


En fait, const_cast<> sert à appeler des fonctions déclarées
non-const à tort.


Si je comprends bien, on ne devrait jamais avoir à tuliser ce
type de casts...


Il y a des cas exceptionnels...

Historiquement, il n'y avait pas mutable, et un objet n'était
jamais const s'il avait des constructeurs définis par
l'utilisateur. On utilisait aussi une conversion explicite (qui
revenait à un const_cast aujourd'hui) pour faire ce qu'on fait
avec mutable aujourd'hui. Dans certains cas, on pourrait
envisager même aujourd'hui l'utisation de const_cast à la place
de mutable, à condition qu'au moins un membre soit mutable. (La
différence, c'est que le mutable est global -- il permet à
n'importe quelle fonction const de modifier l'objet. Tandis que
le const_cast ne concerne que la fonction qui l'utilise. Mais il
faut bien au moins un membre mutable pour être sûr que le
const_cast soit légal.)

Il y a aussi des fonctions qui exposent une vue à une partie de
l'objet, et qu'on veut fournir en deux versions, une const et
une autre non. L'exemple type est l'opérateur [] dans une
collection, où on va déclarer :
T& operator[]( int index ) ;
T const& operator[]( int index ) const ;
Pour peu que le traitement dans l'opérateur soit non-trivial, on
aimerait la facteuriser dans une seule fonction. Seulement, si
cette fonction est const, il nous faut une const_cast pour créer
la référence non-const ; une implémentation classique de la
version non-const serait :
T&
Collection::operator[]( int index )
{
return const_cast< T& >(
const_cast< T const* >( this)->operator[]( index ) ) ;
}
On rémarque deux const_cast, le premier (en termes d'exécution)
qui ajoute une const, pour modifier la résolution du surcharge
(et éviter ici une récursion infinie), et la deuxième pour
enlever le const du résultat. Le premier est clairement légal,
on ajoute un const. Et on sait que le deuxième est légal, parce
qu'il se trouve dans une fonction non-const, qui n'a pas pû être
appelé sur un objet const.

Enfin, comme Fabien a dit, on a souvent affaire à une interface
avec des logiciels plus anciens (surtout en C), qui ne sont pas
rigueureux dans leur utilisation de const. Considère même
Posix@: write prend bien un « void const* », comme il se veut,
mais writev prend un « iovec const* », et l'iovec contient des
« void* », et non des « void const* » (pour la simple raison
qu'il sert aussi à readv). Et ça, c'est dans une interface qui a
été révue pour la bonne utilisation de const. (C'est d'ailleurs
un bon exemple du genre de problème qui peut se poser. readv et
writev existaient bien avant const, et utilisaient -- tout
naturellement alors -- la même struct iovec. Alors, comment
définir un iovec qui est compatible avec les deux ?)

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


1 2 3