OVH Cloud OVH Cloud

Constantes modifiables en C++

20 réponses
Avatar
Pierre Maurette
Bonjour,
Il est fréquent que des variables (de tous types) soient initialisées
par la lecture d'un fichier INI par exemple, voire un calcul ou
l'appel d'une fonction en début de programme.
Il peut arriver que ces "constantes" soient modifiables
"exceptionnellement" par un utilisateur identifié (on peut alors se
ramener au précédent, par clôture/initialisation).
Il me semble normal de conserver le statut const à ces variables. Pour
l'instant, j'utilise quelques cast, et ça roule.

J'aimerais connaître la stratégie des uns et des autres face à ce
petit problème.
--
Merci,

Pierre

10 réponses

1 2
Avatar
Falk Tannhäuser
int Nom_a_la_con_pour_ecrire = 123;
const int* P = &Nom_a_la_con_pour_ecrire;
#define Param (*P)
ou plus court et sans préprocesseur :

int Nom_a_la_con_pour_ecrire = 123;
int const& Param = Nom_a_la_con_pour_ecrire;
// bla bla
//Param = 5; // Refusé
std::cout << Param << std::endl;


Falk

Avatar
Falk Tannhäuser
Jean-Marc Bourguet wrote:
Si tu veux jouer a ca, je prefere

int Nom_a_la_con_pour_ecrire = 123;
int& Nom_a_la_con_pour_lire = Nom_a_la_con_pour_ecrire;

(Mais bon, il y a toujours le probleme de l'ordre d'initialisation des
statiques).
???

À l'intérieur d'une même unité de compilation, cela ne devrait
jamais poser problème, non ? Puis, pour les entiers et les références
statiques ou globaux, il n'y a pas besoin d'exécuter des constructeurs
/ du code d'initialisation...

Falk

Avatar
Pierre Maurette
Jean-Marc Bourguet typa:

Pierre Maurette writes:
[...]

Par exemple, celui-là est correct, je ne l'avais pas posté, parce
qu'un peu gag:

int Nom_a_la_con_pour_ecrire = 123;
const int* P = &Nom_a_la_con_pour_ecrire;
#define Param (*P)
// bla bla
//Param = 5; // Refusé
std::cout << Param << std::endl;


Si tu veux jouer a ca, je prefere

int Nom_a_la_con_pour_ecrire = 123;
int& Nom_a_la_con_pour_lire = Nom_a_la_con_pour_ecrire;
cons int& Nom_a_la_con_pour_lire = Nom_a_la_con_pour_ecrire;

;-)
De mauvaise foi, je dirais que j'avais proposé un truc qui fonctionne
en C comme en C++.
La vérité, c'est que j'ai le réflexe pointeur.
--
Pierre


Avatar
Pierre Maurette
typa:

Pierre Maurette wrote in message
news:...
typa:

Pierre Maurette wrote in message
news:...

Il est fréquent que des variables (de tous types) soient
initialisées par la lecture d'un fichier INI par exemple, voire un
calcul ou l'appel d'une fonction en début de programme. Il peut
arriver que ces "constantes" soient modifiables
"exceptionnellement" par un utilisateur identifié (on peut alors se
ramener au précédent, par clôture/initialisation). Il me semble
normal de conserver le statut const à ces variables. Pour
l'instant, j'utilise quelques cast, et ça roule.


Je suis curieux comment. Si j'écris :

extern int const i = 0 ;

et j'essaie plus tard à modifier la variable, au moyen de :

const_cast< int& >( i ) = 1 ;

j'ai un comportement indéfini, qui en fait ne marche pas toujours.


Je faisais parfois des trucs comme:
const int tt = 0;
*const_cast<int*>(&tt) = 20;
*(int*)(&tt) = 21;
Ça passe sans avertir sur mes compilos.
Peut-être "par hasard" ? ;-)


Tout à fait. Et il existe bien des cas et des implémentations où ça ne
marche pas -- ou bien, le compilateur voir que tt est const, et
l'utilise directement la valeur d'initialisation, plutôt que de lire la
variable, ou bien, le compilateur met la variable carrément dans un
segment protégé en écriture, qui donne un core à l'affectation.

Note bien que c'est parce que l'objet même est const. Quelque chose du
genre :

int maVar = 0 ;
int const& v = maVar ;

*const_cast< int* >( &maVar ) = 20 ;

est parfaitement légal.
Ce qui est à mon sens un peu embêtant, c'est que les compilateurs

testés n'avertissent pas, et ignorent l'afffection. Si 3 compialteurs
font la même chose, il y a peut-être une raison que m'échappe.
(j'ai vérifié que ce n'est pas l'OS qui zappe l'instruction, comme il
le fait effectivement de certaines instructions privilégiées)
Pierre




Avatar
Pierre Maurette
Pierre Maurette typa:
[...]
int Nom_a_la_con_pour_ecrire = 123;
int& Nom_a_la_con_pour_lire = Nom_a_la_con_pour_ecrire;
cons int& Nom_a_la_con_pour_lire = Nom_a_la_con_pour_ecrire;

Oooops !

const int& Nom_a_la_con_pour_lire = Nom_a_la_con_pour_ecrire;
--
Pierre


Avatar
kanze
Falk Tannhäuser wrote in message
news:...
Pierre Maurette wrote:
Je faisais parfois des trucs comme:
const int tt = 0;
*const_cast<int*>(&tt) = 20;
*(int*)(&tt) = 21;
Ça passe sans avertir sur mes compilos.
Peut-être "par hasard" ? ;-)


const int tt = 0;
int main()
{
*const_cast<int*>(&tt) = 20;
// ou simplement
//const_cast<int&>(tt) = 21;
return 0;
}

chez moi, ça passe la compilation sans broncher (gcc 3.3.1 Cygwin
sous Windows 2000) - le const_cast est là pour dire au compilo
de la fermer !
Par contre, à l'exécution, ce programme m'affiche "Signal 11"
(sous Linux: "Memory fault") sans qu'il y ait un std::cout
ou printf dedans - magique non ?


Il me semble avoir déjà dit : c'est un comportement indéfini.

Un cas plus intéressant serait un const avec un constructeur défini par
l'utilisateur. Jusqu'à l'ARM et CFront 3.1, au moins, on avait le droit
de le modifier. C'est avec l'introduction de mutable qu'on a changé la
règle -- si une classe a un ou plusieurs éléments mutable, on a droit à
en modifier un objet const.

Dans la pratique, tous les compilateurrs que je connais applique encore
l'ancienne règle. Pas seulement pour des raisons de compatibilité ;
l'objet ne peut pas être const pendant sa construction, et la plupart
des systèmes n'ont aucune possibilité de le rendre réelement const par
la suite.

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


Avatar
kanze
"Michel Michaud" wrote in message
news:<4OPhc.64836$...
Dans news:, Pierre
Je faisais parfois des trucs comme:
const int tt = 0;
*const_cast<int*>(&tt) = 20;
*(int*)(&tt) = 21;
Ça passe sans avertir sur mes compilos.
Peut-être "par hasard" ? ;-)
Mais peu importe, voir ma réponse au post de Falk.


Pour être clair et précis : un const_cast qui enlève un const n'est
correct (i.e. pas « undefined ») seulement si on sait que l'objet
n'est pas const au départ.


Pour être plus clair et plus précis : un const_cast qui enlève un const
est toujours correct. En utiliser le résultat pour essayer de modifier
un objet const ne l'est pas. Donc :

int
main()
{
static int const i = 42 ;
std::cout << const_cast< int& >( i ) << 'n' ;
return 0 ;
}

et parfaitement légal. Stupide, peut-être, mais légal.

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


Avatar
Maxim
Falk Tannhäuser wrote:
Par contre, à l'exécution, ce programme m'affiche "Signal 11"
(sous Linux: "Memory fault") sans qu'il y ait un std::cout
ou printf dedans - magique non ?


C'est votre shell qui informe de ca.

--
Maxim

Avatar
kanze
Pierre Maurette wrote in message
news:...

[...]
Note bien que c'est parce que l'objet même est const. Quelque chose
du genre :

int maVar = 0 ;
int const& v = maVar ;

*const_cast< int* >( &maVar ) = 20 ;

est parfaitement légal.


Ce qui est à mon sens un peu embêtant, c'est que les compilateurs
testés n'avertissent pas, et ignorent l'afffection. Si 3 compialteurs
font la même chose, il y a peut-être une raison que m'échappe. (j'ai
vérifié que ce n'est pas l'OS qui zappe l'instruction, comme il le
fait effectivement de certaines instructions privilégiées)


C'est un comportement indéfini parce que le compilateur ne peut pas
toujours savoir quand l'objet est réelement const. Et que les objets
const, le compilateur a le droit de les mettre en mémoire protégée (ou
en ROM dans les processeurs embarqués -- pas de core, mais la valeur ne
change pas).

Puisque c'est un comportement indéfini, il faut s'attendre à ce que ce
comportement varie. Pour des raisons historiques, ça m'étonnerait pas,
par exemple, que beaucoup des premiers compilateurs à implémenter const
mettaient des variables const dans le segment de données ; par la suite,
on continue parce qu'on hésite à casser du code qui a marché, même si en
fait, il était cassé selon la norme déjà.

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


Avatar
Michel Michaud
Dans news:,
"Michel Michaud" wrote in message
news:<4OPhc.64836$...
Pour être clair et précis : un const_cast qui enlève un const n'est
correct (i.e. pas « undefined ») seulement si on sait que l'objet
n'est pas const au départ.


Pour être plus clair et plus précis : un const_cast qui enlève un
const est toujours correct. En utiliser le résultat pour essayer de
modifier un objet const ne l'est pas. Donc :


Exact. Tu as raison de préciser. On pourrait aussi indiquer la
règle par l'autre bout de la lorgnette : il y a comportement
indéfini si on essaie de modifier un objet const¹ par un moyen
détourné. Il me semble que c'est la vision la plus logique, parce
que si tu ne veux pas utiliser le résultat pour modifier l'objet,
pourquoi faire le const_cast ? (je vois bien des possibilités,
mais elle suppose un mauvais design de certaines fonctions)

¹ S'il n'est pas aussi déclaré mutable...

--
Michel Michaud
http://www.gdzid.com
FAQ de fr.comp.lang.c++ :
http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ/


1 2