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

10 réponses

1 2
Avatar
Michel Decima
Marc G wrote:

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 ?


Ca ne me semble pas stupide, par contre je ne vois pas tres bien
l'avantage de la classe template CAutoRun vis-a-vis d'une solution
moins elaboree:

// dans cpp:

int une_fonction(void) {
X::init_map();
// ...
return 0;
}

int static_init = une_fonction();

Mais j'ai peut-etre raté quelque-chose...

Avatar
Marc G

Mais j'ai peut-etre raté quelque-chose...

non, tu n'as rien raté

la réponse est bien connue :
1 ) pourquoi faire simple quand on peut faire compliqué.
2) et puis comme je découvre les template depuis 3-4 mois, j'en use et j'en
abuse (dans le cas présent, c'est pas trop grave)

Mais c'est vrai que c'est le genre de petite astuce qui gagne à être connue.
J'ai souvent vu des initialisations "compliquées" avec des données membres
static du style
static bool already_initialized;
puis un test dans le constructeur.
C'est pas trop élégant et très inefficace si c'est un petit objet souvent
créé.

En tout cas, merci pour ta réponse.
Marc

Avatar
Michael DOUBEZ
Marc G wrote:

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 {
[snip]


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 ?


Ca ne me semble pas stupide, par contre je ne vois pas tres bien
l'avantage de la classe template CAutoRun vis-a-vis d'une solution
moins elaboree:

// dans cpp:

int une_fonction(void) {
X::init_map();
// ...
return 0;
}

int static_init = une_fonction();

Mais j'ai peut-etre raté quelque-chose...



L'ordre des initialisation des statiques plus le fait que si personne
n'utilise la map, elle sera quand même inclue à cause de cette utilisation.

Une solution classique est:

class X {

public:

//fonction retournant le singleton de class
static std::map<...>& getMap();

private:
//fonction d'initialisation
static bool init_map(std::map<...>& m);
};

std::map<...>& X::getMap()
{
//build at first call
static std::map<...> objet_map;
//called once
static bool initialized=init_map(&objet_map);

return objet_map;
}

Ensuite, au lieu d'appeler X::objet_map, appeler X::getMap().

Michael


Avatar
Michel Decima
Marc G wrote:
Mais j'ai peut-etre raté quelque-chose...

non, tu n'as rien raté

la réponse est bien connue :
1 ) pourquoi faire simple quand on peut faire compliqué.
2) et puis comme je découvre les template depuis 3-4 mois, j'en use et j'en
abuse (dans le cas présent, c'est pas trop grave)


Je crois que c'est une bonne reponse ;)

Mais c'est vrai que c'est le genre de petite astuce qui gagne à être connue.


dans le code que j'ai proposé, j'ai modifié "void une_fontion()" en "int
une_fonction()", la valeur de retour ne servant qu'a initialiser l'objet
global. Si tu ne veux/peux pas modifier le prototype de la fonction, tu
peux aussi faire ceci:

void une_fonction() { ... }
bool static_init = ( une_fonction(), true );

Mais je ne suis pas sur que l'usage de l'operateur virgule + les
parentheses rende le tout plus propre...

J'ai souvent vu des initialisations "compliquées" avec des données membres
static du style
static bool already_initialized;
puis un test dans le constructeur.
C'est pas trop élégant et très inefficace si c'est un petit objet souvent
créé.


Et ca deviendra interessant avec des threads...

En tout cas, merci pour ta réponse.


De rien.


Avatar
Marc G
Une solution classique est:

class X {

public:

//fonction retournant le singleton de class
static std::map<...>& getMap();

private:
//fonction d'initialisation
static bool init_map(std::map<...>& m);
};

std::map<...>& X::getMap()
{
//build at first call
static std::map<...> objet_map;
//called once
static bool initialized=init_map(&objet_map);

return objet_map;
}

Ensuite, au lieu d'appeler X::objet_map, appeler X::getMap().


une question subsidiaire pour moi
la fonction init_map, elle n'est appelée qu'une fois, lors de la première
initialisation de la variable static initialized ?
ou elle est appelée à chaque fois ?

dans mon cas, l'objet map est "très" utilisé donc il doit être construit de
toutes façons.
mais ta réponse est intéressante.

Avatar
James Kanze
On Mar 13, 9:41 pm, Michel Decima wrote:
Marc G wrote:
Mais j'ai peut-etre raté quelque-chose...


Mais c'est vrai que c'est le genre de petite astuce qui
gagne à être connue.


dans le code que j'ai proposé, j'ai modifié "void une_fontion()" en " int
une_fonction()", la valeur de retour ne servant qu'a initialiser l'objet
global. Si tu ne veux/peux pas modifier le prototype de la fonction, tu
peux aussi faire ceci:

void une_fonction() { ... }
bool static_init = ( une_fonction(), true );

Mais je ne suis pas sur que l'usage de l'operateur virgule + les
parentheses rende le tout plus propre...


En général, je serais d'accord ; je ne suis vraiment pas fana
de l'opérateur virgule. Mais dans ce cas ci, je crois que c'est
en voie de devenir un idiome « classique ». Alors, dans la
mesure où la variable a un nom qui indique bien son but (j'aime
« dummyForInitialization » moi-même, mais « static_init »
est déjà assez parlant), je l'accepte.

Ceci dit... S'il ne s'agit que d'initialiser un map, la plupart
du temps, j'utilise plutôt quelque chose du genre :

namespace {

struct Init
{
// ...
// Que des types POD, de façon à ce que
// l'initialisation par {...} fonctionne.
// ...
operator std::map<...>::value_type() const
{
return std::map<...>::value_type( ... ) ;
}
} ;

Init const initTable[] =
{
{ ... },
{ ... },
// ...
}
}
std::map<...> X::object_map(
begin( initTable ), end( initTable ) ) ;

Du coup, pas de fonction à part. Et même, le map peut être
déclaré const, de façon à être (plus) sûr de ne jamais le
modifier par la suite. (Si c'est ce qu'on veut, évidemment.)

J'ai souvent vu des initialisations "compliquées" avec des données membres
static du style
static bool already_initialized;
puis un test dans le constructeur.
C'est pas trop élégant et très inefficace si c'est un petit objet souvent
créé.


Et ca deviendra interessant avec des threads...


J'ai mes doutes en ce qui concerne le « très inefficace ».
J'utilise assez les singletons, sans jamais avoir rencontré de
problème de performance. En ce qui concerne les threads, il
suffit en fait que le premier appel ait eu lieu avant le
démarrage des threads. Pour cela, une initialisateur d'un static
fait bien l'affaire.

(Mais je ne préconsise cette solution que dans le cas où il y a
une risque que des constructeurs d'autres objets statiques se
servent de ton objet, et qu'il faut donc gérer l'ordre
d'initialisation. Sinon, la construction statique, comme
ci-dessus, fait bien l'affaire.)

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



Avatar
James Kanze
On Mar 13, 9:33 pm, Michael DOUBEZ wrote:

[...]
L'ordre des initialisation des statiques plus le fait que si personne
n'utilise la map, elle sera quand même inclue à cause de cette utilis ation.

Une solution classique est:

class X {
public:
//fonction retournant le singleton de class
static std::map<...>& getMap();

private:
//fonction d'initialisation
static bool init_map(std::map<...>& m);
};

std::map<...>& X::getMap()
{
//build at first call
static std::map<...> objet_map;
//called once
static bool initialized=init_map(&objet_map);

return objet_map;

}

Ensuite, au lieu d'appeler X::objet_map, appeler X::getMap().


C'est assez proche du singleton. Pourquoi pas en faire une
véritable singleton :

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

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

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

Avatar
Michael DOUBEZ
Une solution classique est:

class X {

public:

//fonction retournant le singleton de class
static std::map<...>& getMap();

private:
//fonction d'initialisation
static bool init_map(std::map<...>& m);
};

std::map<...>& X::getMap()
{
//build at first call
static std::map<...> objet_map;
//called once
static bool initialized=init_map(&objet_map);

return objet_map;
}

Ensuite, au lieu d'appeler X::objet_map, appeler X::getMap().


une question subsidiaire pour moi
la fonction init_map, elle n'est appelée qu'une fois, lors de la première
initialisation de la variable static initialized ?
ou elle est appelée à chaque fois ?


Une seule fois, au premier appel pour initialiser la variable statique.


dans mon cas, l'objet map est "très" utilisé donc il doit être construit de
toutes façons.
mais ta réponse est intéressante.


Si il est très utilisé en en particulier dans une autre variable
statique, cette solution te garantis que la variable est initialisé au
moment où tu l'utilises (sans te préoccuper de l'ordre de compilation
des objets en fonction des compilateurs et des environnements).

Michael


Avatar
Michael DOUBEZ
On Mar 13, 9:33 pm, Michael DOUBEZ wrote:

[...]
L'ordre des initialisation des statiques plus le fait que si personne
n'utilise la map, elle sera quand même inclue à cause de cette utilisation.

Une solution classique est:

class X {
[snip]..
};

Ensuite, au lieu d'appeler X::objet_map, appeler X::getMap().


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.


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.


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

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

Michael


Avatar
James Kanze
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.

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.

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

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.

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.

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