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

Initialisation d'une donnée membre static

14 réponses
Avatar
Marc G
C'est un pb classique
je me le suis posé pour des vector et maintenant je suis confronté au même
pb avec un objet map.

je ne veux pas utiliser de donnée membre static comme marqueur pour savoir
une initialisation a eu lieu et tester si c'est le cas dans chaque appel de
constructeur.
j'ai eu l'idée de définir une classe template toute simple qui me permet
d'initialiser facilement des données membres static.
Plus concrètement :-)

voici la définition du modèle (le nom du modèle est un peu exagéré :-))

template<typename T>
class CAutoRun {
public :
CAutoRun(T const& t) { (*t)();}
~CAutoRun() {}
};
typedef CAutoRun<void(*)(void)> auto_run;


dans hpp

class X {

public :


static void init_map(void) { ...}
static std::map<...> objet_map;

};

dans cpp

void une_fonction(void) {
X::init_map();
...
}
auto_run init_static(une_fonction);

nota : X est pour moi une classe template et dans une_fonction, j'initialise
plusieurs modèles en même temps
L'idée est simplement qu'on ne peut pas faire exécuter du code dans cpp,
mais qu'on peut créer des objets "globaux".
Je crée donc un objet qui exécute le code que je veux et il n'est créé
qu'une fois et en plus dans le bon module (je veux dire que l'ordre de
compilation des modules n'a pas d'importance)
Je voudrais avoir votre avis sur le principe de ce modèle.
Est-ce stupide ou courant ?
Merci à vous
Marc

4 réponses

1 2
Avatar
Michael DOUBEZ
On Mar 14, 10:40 am, Michael DOUBEZ wrote:
On Mar 13, 9:33 pm, Michael DOUBEZ wrote:
[...]
C'est assez proche du singleton. Pourquoi pas en faire une
véritable singleton :


C'est un singleton avec une fonction custom d'initalisation à cause
de l'initialisation statique des variables.


Je ne comprends pas trop. Est-ce que c'est un singleton ou
non ? Moi, ce que je voyais, c'est que le map se comporte comme
un singleton. La plus simple, alors, serait de le wrapper pour
faire l'initialisation, et ensuite, le gerer comme un singleton.


Je voulais garder le principe d'une static de class de l'OP sans
allocation par new.
C'est un singleton (de mon point de vue).


std::map<...>&
X::get()
{
static::map<...>* theOneAndOnly = createMap() ;
return *theOneAndOnly ;
}

avec :
std::map<...>*
createMap()
{
std::map<...>* result = new std::map<...> ;
// initialisation du map...
return result ;
}


Oui, c'est beaucoup plus clair et peut être avec un auto_ptr<> pour être
100% propre.


Au contraire. Tout l'intérêt de cette solution, c'est que
l'objet n'est jamais detruit. Ou surtout, si on préfère, qu'il
n'est pas détruit avant que quelqu'un s'en sert dans le
destructeur d'un autre objet statique.


Un programme/OS est garanti de rendre toutes les ressources a la
terminaison ? J'ai des souvenirs de code sous Windows 3.1 qui polluaient
la mémoire.


Ça a aussi l'avantage d'éliminer les risques dues à l'ordre de
destruction. (J'avoue ne jamais avoir eu de problèmes de cette
côté-là, mais en principe, elles existent.)

Enfin, il faut faire gaffe aux threads dans ces cas-là. La
construction des statiques locales n'est pas thread safe, au
moins dans les compilateurs courants. Du coup, il faudrait soit
s'assurer que la fonction est appelée au moins une fois avant le
démarrage des threads, soit y introduire des locks.


J'attends avec impatience le prochain standard qui je l'espère apportera
des solutions élégantes aux statiques partagées.


Je ne sais pas ce que tu attends. Une solution générale coûte
assez chère, et n'est que rarement nécessaire. Alors, selon le
principe de ne pas payer pour ce dont tu ne te sers pas...


Un simple mutex standard pouvant être initialisé de façon statique et
atomique me convient.

Maintenant, si un nouveau keyword me permet d'assurer qu'une
initialisation static ne se fait qu'une fois ou même qu'une opération se
fait de façon atomique, c'est royal.

MyClass& my_singleton()
{
atomic static Myclass* single=init_single();

return *single;
}



Il me semble que gcc 4.x inclut automatiquement un lock pour protégé
l'initialisation des statiques.


Non. Il génère un espèce de bricolage qui ne marche pas sur les
plateformes que je connais. Je le désactive systèmatiquement,
pour éviter des problèmes.


Je suis resté à gcc 3.4. C'est bon à savoir.


C'est vrai que sous les Unix, il aurait pu se servir de
pthread_once, qui lui est garanti de marcher. A priori, on
s'attendrait aussi à ce que les implémenteurs du système aient
utiliser la solution la plus efficace pour le système. A
priori... j'avoue qu'il m'est arrivé d'être deçu dans ce genre
d'attente dans la passée.



Michael



Avatar
Michael DOUBEZ
[snip]
Au contraire. Tout l'intérêt de cette solution, c'est que
l'objet n'est jamais detruit. Ou surtout, si on préfère, qu'il
n'est pas détruit avant que quelqu'un s'en sert dans le
destructeur d'un autre objet statique.


Un programme/OS est garanti de rendre toutes les ressources a la
terminaison ? J'ai des souvenirs de code sous Windows 3.1 qui polluaient
la mémoire.


En fait, la question ne se pose pas aujourd'hui dans la plupart des
système. C'est juste que j'aime tout nettoyer en sortant :)

Michael


Avatar
Sylvain
Michael DOUBEZ wrote on 14/03/2007 22:44:
[snip]
Au contraire. Tout l'intérêt de cette solution, c'est que
l'objet n'est jamais detruit. Ou surtout, si on préfère, qu'il
n'est pas détruit avant que quelqu'un s'en sert dans le
destructeur d'un autre objet statique.


Un programme/OS est garanti de rendre toutes les ressources a la
terminaison ? J'ai des souvenirs de code sous Windows 3.1 qui
polluaient la mémoire.


En fait, la question ne se pose pas aujourd'hui dans la plupart des
système. C'est juste que j'aime tout nettoyer en sortant :)


le pb n'a jamais été ce qu'il advint lorsque votre code est entièrement
déchargé de la mémore, et James ne parlait pas de cela.

le pb est strictement limité aux objets de votre code, si ces objets
utilisent un singleton (à vocation quelconque) jusqu'à leur dernier
souffle, ce singleton devra survivre au dernier objet ...

Sylvain.



Avatar
James Kanze
On Mar 14, 10:29 pm, Michael DOUBEZ wrote:

On Mar 14, 10:40 am, Michael DOUBEZ wrote:
On Mar 13, 9:33 pm, Michael DOUBEZ wrote:
[...]
C'est assez proche du singleton. Pourquoi pas en faire une
véritable singleton :


C'est un singleton avec une fonction custom d'initalisation à cause
de l'initialisation statique des variables.


Je ne comprends pas trop. Est-ce que c'est un singleton ou
non ? Moi, ce que je voyais, c'est que le map se comporte comme
un singleton. La plus simple, alors, serait de le wrapper pour
faire l'initialisation, et ensuite, le gerer comme un singleton.


Je voulais garder le principe d'une static de class de l'OP sans
allocation par new.


Des raisons particulières, ou juste comme ça.

L'intérêt du static, dans de tels cas, c'est surtout quand il
peut y avoir une initialisation statique aussi ;
l'initialisation statique garantit l'absence des problèmes dus à
l'ordre d'initialisation. Dès que l'initialisation n'est pas
statique, en revanche, tu as le choix : tu peux déclarer
l'objet même statique, et laisser le choix au compilateur de
quand il sera initialisé, ou tu peux t'en occuper toi-même, mais
pour ce faire, il faut que l'objet soit alloué dynamique. Il n'y
a que les objets alloués dynamiquement dont tu peux commander
toi-même la durée de vie.

C'est un singleton (de mon point de vue).


Logiquement, c'est ce que je voulais savoir. Alors, je ne vois
pas l'intérêt d'en cacher le fait.

Il y a, à mon avis, plusieurs solutions standard pour les
« singletons » : les objets statiques, avec initialisation
statique ou initialisation dynamique, et des variants du modèle
de conception « singleton ». J'éviterais d'autres solutions,
simplement parce que je crois que ces solutions permettent de
couvrir tous les cas nécessaires, et qu'elles sont connues.

Parmis ces solutions, si on peut s'en tirer avec
l'initialisation statique, la première est de loin préférable :
l'objet est simplement là, pendant toute la durée du programme,
correctement initialisé avant la première ligne de code
s'exécute, et jamais détruit. Malheureusement, tous les types
d'objet ne s'y prêtent pas. (Mais peut-être en modifiant le
type ? J'utilise souvent des tableaux de type C, des struct's
POD, trié déjà dans les sources, et std::lower_bound, à la place
d'un std::map. Précisement parce que un tableau de type C, avec
des structs POD, permet une initialisation statique.)

Pour moi, un objet statique à l'initialisation ne vaut que si la
nature même de l'objet fait qu'on ne se servira qu'une fois
l'application bien lancée, et qu'on ne se servira pas une fois
l'arrêt a démarré. Quelque chose du genre d'un map des
identificateurs de service vers des fonctions qui effectuent le
boulot, par exemple, où les identificateurs de services
proviennent des messages qu'on reçoit des clients (et que le
boulot à effectuer présuppose de toute façon une application en
pleine marche). Et encore, tout dépend de comment j'initialise
l'objet. Très souvent, les « fonctions » sont en fait des
objets fonctionnels, dont les instances sont statiques, et qui
s'inscrire eux-même automatiquement dans le map. Du coup, le map
même sert aussi pendant l'initialisation, et une instance
statique n'en va pas.

Dans une contexte single-thread, j'aime l'implémentation du
singleton où l'objet même est une variable statique locale. Ça
ne résoud toujours pas les problèmes de l'ordre de destruction,
mais en général, je m'arrange pour que mes destructeurs n'aient
pas besoin de tels objets. Sinon, j'utilise un pointeur déclaré
comme variable statique locale, initialisé par une expression de
new, afin que le destructeur n'en soit jamais appelé.

Dans une contexte multi-thread, j'utilise la plupart du temps
les mêmes solutions, tout en m'assurant que la fonction de
création du singleton soit appelé au moins une fois lors des
initialisations statiques, pour éviter la nécessité d'un lock.
Une solution simple pour y parvenir, c'est de déplacer le
pointeur en dehors de la fonction, et de le déclare
« initialisé » par la fonction qui le renvoie :

namespace {
Singleton* objet = &Singleton::instance() ;
}

Singleton&
Singleton::instance()
{
if ( objet == NULL ) {
objet = new Singleton ;
// ou...
// static Singleton lObjet ;
// objet = &lObjet ;
}
return objet ;
}

C'est un peu subtile, parce qu'il compte sur l'initialisation
avec null avant toute initialisation dynamique, mais il marche
très bien.

std::map<...>&
X::get()
{
static::map<...>* theOneAndOnly = createMap() ;
return *theOneAndOnly ;
}

avec :
std::map<...>*
createMap()
{
std::map<...>* result = new std::map<...> ;
// initialisation du map...
return result ;
}


Oui, c'est beaucoup plus clair et peut être avec un auto_ptr<> pour être
100% propre.


Au contraire. Tout l'intérêt de cette solution, c'est que
l'objet n'est jamais detruit. Ou surtout, si on préfère, qu'il
n'est pas détruit avant que quelqu'un s'en sert dans le
destructeur d'un autre objet statique.


Un programme/OS est garanti de rendre toutes les ressources a la
terminaison ?


Garanti par qui ? La norme C++ ne garantit rien de ce qui se
passe une fois que tu as laissé le programme C++. Dans la
pratique, je ne connais pas d'OS qui rend toutes les
ressources ; a priori, je ne suis même pas sûr qu'il soit
possible (mais les OS pouvaient faire mieux que ne font Unix).

La question ici, évidemment, c'est si le singleton utilise de
telles ressources. Mes singletons n'en utilisent pas. Donc, pas
de problème. Si le singleton s'en sert, en revanche, disons
parce qu'il crée un fichier temporaire, il faudrait bien en
appelé le destructeur. Avec la risque d'un problème dans l'ordre
de destruction en plus.

Note, par exemple, que la norme C++ exige que les objets de type
std::cin, std::cout et std::cerr ne soit jamais détruit. Elle
garantit seulement qu'il y aura un flush quelque part pendant
l'exécution des destructeurs statiques (mais pas forcément après
le dernier destructeur statique).

Ça a aussi l'avantage d'éliminer les risques dues à l'ordre de
destruction. (J'avoue ne jamais avoir eu de problèmes de cette
côté-là, mais en principe, elles existent.)

Enfin, il faut faire gaffe aux threads dans ces cas-là. La
construction des statiques locales n'est pas thread safe, au
moins dans les compilateurs courants. Du coup, il faudrait soit
s'assurer que la fonction est appelée au moins une fois avant le
démarrage des threads, soit y introduire des locks.


J'attends avec impatience le prochain standard qui je l'espère appor tera
des solutions élégantes aux statiques partagées.


Je ne sais pas ce que tu attends. Une solution générale coûte
assez chère, et n'est que rarement nécessaire. Alors, selon le
principe de ne pas payer pour ce dont tu ne te sers pas...


Un simple mutex standard pouvant être initialisé de façon statique et
atomique me convient.


Ce qui est assez simple à implémenter sous Unix (il y a un macro
pour l'initialisation statique de pthread_mutex_t), mais pas
possible sous Windows, d'après ce que j'ai compris (mais je ne
suis pas spécialiste Windows -- il y a donc peut-être un truc
qui je n'ai pas vu).

Maintenant, si un nouveau keyword me permet d'assurer qu'une
initialisation static ne se fait qu'une fois ou même qu'une opération se
fait de façon atomique, c'est royal.


C'est effectivement la solution que je préfèrerais aussi. Que
par défaut, pas de garanti, mais qu'on puisse dire au
compilateur qu'on aimerait en avoir. (Ou l'inverse -- c'est
plus sûr, mais de l'autre côté, je n'ai pour ainsi dire jamais
besoin de la garantie... parce que mon code jusqu'ici a toujours
été écrit où la possibilité de l'avoir n'existait de toute façon
pas:-).)

Mais toutes les propositions sont encore assez volatile en ce
qui concerne les threads. Je ne me hazarderais pas à dire ce
qu'on va avoir (sauf que le modèle de base prendrait en compte
les threads).

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




1 2