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

Variable locale "presque static"

13 réponses
Avatar
Fabien LE LEZ
Bonjour,

Supposons que dans une fonction f(), j'aie besoin d'une variable
locale qui garde sa valeur d'un appel à l'autre.
Il y a une solution simple, propre et élégante : la déclarer "static".

// x.h

void f();


// x.cpp

void f()
{
static unsigned int nb_appels= 0;
cout << "La fonction f() a été appelée "
<< (++nb_appels) << " fois \n";
}


La variable "nb_appels" est un détail d'implémentation, on n'en parle
pas du tout dans le .h, tout va bien.



Supposons maintenant que f() soit membre d'une classe C, et que j'aie
besoin d'une variable locale, qui garde sa valeur à d'un appel à
l'autre, mais spécifique à chaque instance de C.
Avec bien sûr le même cahier des charges que précédemment :
- la variable doit être locale à la fonction, ou au moins
invisible par les autres fonctions ;
- on doit pouvoir ajouter une telle variable sans modifier le .h
(i.e. la définition de la classe) ; idéalement, on ne devrait en
parler nulle part en-dehors de la définition de C::f().


Et là, je coince.
Il y a bien la solution bâtarde de mettre cette variable comme membre
de C, mais alors il faut la déclarer dans le .h, éventuellement
l'initialiser dans le constructeur de C, et elle est loin d'être
locale à C::f().

Une autre solution serait de transformer f() en un foncteur, i.e. un
objet qui contient cette variable et un operateur "()".
Au moins, la variable est inaccessible à autre chose que f(), mais ça
change profondément la définition de C.


Si quelqu'un a une idée pour résoudre élégamment ce problème...
Merci d'avance !

10 réponses

1 2
Avatar
kanze
Fabien LE LEZ wrote:

Supposons que dans une fonction f(), j'aie besoin d'une variable
locale qui garde sa valeur d'un appel à l'autre.
Il y a une solution simple, propre et élégante : la déclarer "stati c".

// x.h

void f();

// x.cpp

void f()
{
static unsigned int nb_appels= 0;
cout << "La fonction f() a été appelée "
<< (++nb_appels) << " fois n";
}

La variable "nb_appels" est un détail d'implémentation, on n'en parle
pas du tout dans le .h, tout va bien.

Supposons maintenant que f() soit membre d'une classe C, et que j'aie
besoin d'une variable locale, qui garde sa valeur à d'un appel à
l'autre, mais spécifique à chaque instance de C.
Avec bien sûr le même cahier des charges que précédemment :
- la variable doit être locale à la fonction, ou au moins
invisible par les autres fonctions ;


Est-ce si important que ça ? Après tout, les autres fonctions vont
partie de la classe aussi -- ce n'est pas les utilisateurs qui les
écrivent.

- on doit pouvoir ajouter une telle variable sans modifier le .h
(i.e. la définition de la classe) ; idéalement, on ne devrait en
parler nulle part en-dehors de la définition de C::f().


Même question ? Ou bien, ton .h contient déjà des données privées,
et
qu'il faut l'éditer chaque fois que tu en modifies quelque chose, ou
bien, tu fais appel à l'idiome du pare-feu de compilation, et alors,
ta
variable va dans la classe Impl, avec les autres variables privées.

Et là, je coince.
Il y a bien la solution bâtarde de mettre cette variable comme membre
de C, mais alors il faut la déclarer dans le .h, éventuellement
l'initialiser dans le constructeur de C, et elle est loin d'être
locale à C::f().


Mais elle reste locale à la classe. L'unité de protection en C++,
c'est
la classe.

Une autre solution serait de transformer f() en un foncteur, i.e. un
objet qui contient cette variable et un operateur "()".


Et d'en avoir une instance du foncteur par instance de la classe. C'est
une possibilité.

Au moins, la variable est inaccessible à autre chose que f(), mais ça
change profondément la définition de C.


Surtout si on en prend déjà l'adresse de la fonction, par exemple
pour
la passer à un algorithme de la STL.

Si quelqu'un a une idée pour résoudre élégamment ce problème...


La plus simple, à mon avis, reste l'ajoute du membre privé à la
classe.
Sinon, si tu sais qu'il n'aurait jamais qu'un nombre restreint
d'instances de la classe créée, tu peux utiliser un std::map
statique,
c-à-d :

void
C::f()
{
static std::map< C*, int >
nb_appels ;
cout << "La fonction C::f() a été appelée
<< (++ nb_appels[ this ])
<< " fois pour l'objet "
<< myId
<< endl ;

}

Mais chaque fois que tu supprimes une instance de C, tu crées une
fuite
de mémoire. Et la risque qu'une nouvelle instance par la suite vient
récupérer la variable de l'instance supprimée, plutôt que d'en
avoir une
nouvelle.

À mon avis, ton problème fondamental, c'est que la durée de vie de
ton
variable pseudo-statique, c'est la durée de vie de l'objet. Ce qui
veut
dire que tu n'échapperas pas à le faire connaître d'une façon à
une
autre au destructeur -- il existe des moyens de gérer l'allocation
dans
la fonction, comme ce que je viens de présenter, mais c'est bien dans
le
constructeur qu'il faut qu'elle soit libérée. Au moins que tu ajoutes
un
paramètre supplémentaire à f pour dire que c'est la dernière fois
qu'on
va l'appeler pour cette objet, et qu'elle doit en supprimer la
variable.
(Ce qui revient à en faire gérer la durée de vie par 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

Avatar
Michael
J'ai une question:

En quoi la fonction suivante ne respecte pas le cahier des charges?

void C::f()
{
static unsigned int nb_appels= 0;
cout << "La fonction f() a été appelée "
<< (++nb_appels) << " fois n";
}

Est-ce que nb_appels est accessible à toutes les instances de C ?
Avatar
Joni
Je n'ai pas essaye mais est-ce ce qui suit ne marcherait pas :

class Cpt
{
private:
int nb;
friend void C::f(); // *seul f() peut modifier nb*
void inc() { ... } // ou operator++
}

class C
{
// ...
Cpt cpt;
void f()
{
cpt.inc();
// ...
}

}
Avatar
adebaene
Fabien LE LEZ wrote:
Bonjour,

Supposons que dans une fonction f(), j'aie besoin d'une variable
locale qui garde sa valeur d'un appel à l'autre.
Il y a une solution simple, propre et élégante : la déclarer "stati c".

// x.h

void f();


// x.cpp

void f()
{
static unsigned int nb_appels= 0;
cout << "La fonction f() a été appelée "
<< (++nb_appels) << " fois n";
}


La variable "nb_appels" est un détail d'implémentation, on n'en parle
pas du tout dans le .h, tout va bien.



Supposons maintenant que f() soit membre d'une classe C, et que j'aie
besoin d'une variable locale, qui garde sa valeur à d'un appel à
l'autre, mais spécifique à chaque instance de C.
Avec bien sûr le même cahier des charges que précédemment :
- la variable doit être locale à la fonction, ou au moins
invisible par les autres fonctions ;
- on doit pouvoir ajouter une telle variable sans modifier le .h
(i.e. la définition de la classe) ; idéalement, on ne devrait en
parler nulle part en-dehors de la définition de C::f().


Et là, je coince.
Il y a bien la solution bâtarde de mettre cette variable comme membre
de C, mais alors il faut la déclarer dans le .h, éventuellement
l'initialiser dans le constructeur de C, et elle est loin d'être
locale à C::f().


Si ton seul souçi, c'est de ne pas modifier le header de C, est-ce que
le mieux ne serait pas d'utiliser l'idiome PIMPL pour cette classe?

Arnaud

Avatar
Fabien LE LEZ
On 18 Apr 2006 09:29:16 GMT, Michael
:

En quoi la fonction suivante ne respecte pas le cahier des charges?



"d'une variable locale, [...] spécifique à chaque instance de C."

Avatar
Fabien LE LEZ
On 18 Apr 2006 04:29:09 -0700, :

Note : il ne faut pas citer l'intégralité d'un message. Surtout pour
rajouter deux lignes à la fin.
Cf <http://www.giromini.org/usenet-fr/repondre.html>.

Si ton seul souçi, c'est de ne pas modifier le header de C, est-ce que
le mieux ne serait pas d'utiliser l'idiome PIMPL pour cette classe?


Ben non. Ça serait un changement encore plus radical de la classe.

Si ton seul souçi, c'est de ne pas modifier le header de C,


Ce n'est pas exactement ça. L'idée est qu'ajouter une variable locale
à une fonction membre, ne devrait pas influer sur la définition de la
classe, ni sur une autre fonction de cette classe (je pense en
particulier au constructeur).

Avatar
kanze
Fabien LE LEZ wrote:

L'idée est qu'ajouter une variable locale
à une fonction membre, ne devrait pas influer sur la définition de la
classe, ni sur une autre fonction de cette classe (je pense en
particulier au constructeur).


Et quelle doit être la durée de vie de cette variable ? Qui
l'initialise, quand et comment ? Qui le libère, quand ?

Si la durée de vie coïncide avec celle de l'objet, c'est bien le
rôle du constructeur et du destructeur de l'objet de s'en
occuper, même si la variable ne sert que dans une seule fonction
membre autrement.

--
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
Fabien LE LEZ
On 19 Apr 2006 00:40:38 -0700, "kanze" :

Et quelle doit être la durée de vie de cette variable ? Qui
l'initialise, quand et comment ?


La création de la variable (allocation mémoire et initialisation) se
fait comme pour une variable locale static : au moment du premier
appel de la fonction (pour cet objet).

Qui le libère, quand ?


Bonne question.
Le problème s'est posé dans un cas particulier : classe très grosse et
passablement complexe (d'où mon peu d'enthousiasme à rajouter un
membre), dont très peu d'instances sont créées au cours de la vie de
l'application. Et la variable en question est généralement un int.
Du coup, la destruction de la variable peut se faire un peu quand on
veut[*], mais pas avant la destruction de l'instance de la classe. Ça
peut être à la fin de l'exécution de l'application, quand l'OS libère
la mémoire.


[*] Et au moment où j'écris ça, je ne peux m'empêcher de penser à un
GC.

Avatar
James Kanze
Fabien LE LEZ wrote:
On 19 Apr 2006 00:40:38 -0700, "kanze" :


Et quelle doit être la durée de vie de cette variable ? Qui
l'initialise, quand et comment ?



La création de la variable (allocation mémoire et initialisation) se
fait comme pour une variable locale static : au moment du premier
appel de la fonction (pour cet objet).


Mon histoire avec std::map fera alors l'affaire. Tu utilises [],
avec l'adresse de l'objet comme indice ; la première fois,
l'objet serait initialisé avec son constructeur par défaut
(c-à-d T(), un int sera donc mis à 0).

Qui le libère, quand ?



Bonne question.


Le problème s'est posé dans un cas particulier : classe très
grosse et passablement complexe (d'où mon peu d'enthousiasme à
rajouter un membre), dont très peu d'instances sont créées au
cours de la vie de l'application.


Avec l'histoire du map, la question n'est pas combien
d'instances sera créée, mais combien en sera supprimé.

Et la variable en question est généralement un int. Du coup,
la destruction de la variable peut se faire un peu quand on
veut[*], mais pas avant la destruction de l'instance de la
classe. Ça peut être à la fin de l'exécution de l'application,
quand l'OS libère la mémoire.


Le problème avec l'histoire de map, c'est que si le déstructeur
ne libere pas l'instance de ta variable, tu risques de créer une
deuxième instance de l'objet à la même adresse plus tard, et
trouver l'instance de ta variable qui correspond à l'objet déjà
détruit.

À la rigueur... Tu ajoutes un paramètre bool supplémentaire,
isDelete, avec une valeur par défaut de false, tu appelle la
fonction dépuis le destructeur, avec ce peramètre à true, et si
le paramètre est true, tu supprime l'entrée dans le map, C'est
pas beau, mais parfois, face à l'existant, il faut faire avec.

[*] Et au moment où j'écris ça, je ne peux m'empêcher de
penser à un GC.


Ce qui n'aidera pas forcément -- si l'instance est dans un
std::map, par exemple.

--
James Kanze
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
Fabien LE LEZ
On Wed, 19 Apr 2006 22:31:44 +0200, James Kanze :

Le problème avec l'histoire de map, c'est que si le déstructeur
ne libere pas l'instance de ta variable, tu risques de créer une
deuxième instance de l'objet à la même adresse plus tard, et
trouver l'instance de ta variable qui correspond à l'objet déjà
détruit.


Oui. C'est pour ça que j'aimerais autant éviter.

À la rigueur [...] tu appelle la
fonction dépuis le destructeur, avec ce peramètre à true,


Ben oui mais non. Ça suppose autant de modifications que l'ajout d'une
variable membre (quitte à la rendre inaccessible à d'autres fonctions,
cf solution de Joni).

1 2