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

Ordre d'initialisation

4 réponses
Avatar
Vincent Jacques
Bonsoir à tous,

est-ce que la norme dit que les variables membres statiques sont
initialisée avant les objets globaux? Si oui, inutile de continuer, ça
résout, je crois, mon problème.

Sinon, ben euh je vais essayer d'être synthétique:

Mon application peut recevoir des données de plusieurs sources (port
série, port parallèle...) Mon utilisateur (moi, avec ma casquette
"Utilisateur") doit pouvoir choisir quelle source utiliser. Je veux
également pouvoir ajouter à mon application un nouveau type de source
sans modifier le code existant, juste en ajoutant du code dans de
nouveaux fichiers.

Pour ce faire, j'envisage la structure suivante:

Une classe virtuelle Source et un singleton (celui de ma question d'hier,
pour ceux qui suivent) SelecteurDeSource.

Le constructeur de Source enregistre chaque Source concrète auprès du
SelecteurDeSource. J'accède ensuite aux Sources grace à lui.

Pour ne pas modifier le code existant, je mets la définition et
l'implémentation des Sources concrètes dans un fichier .cpp et dans le
même fichier je déclare une instance globale (de porté le-dit fichier) de
ce type.

Maintenant, le rapport avec la question du début de ce message:

si mes Sources sont initialisées avant la variable membre statique
SelecteurDeSource* SelecteurDeSource::m_Instance
je suis dans de beaux draps, puisque GetInstance() ne créera
(vraisemblablement, ie si m_Instance est non null) pas l'instance et
accèdera à de la mémoire non alouée.

Voili voilou, est-ce que je fais une erreur de conception, est-ce que je
ne connais pas encore assez le C++, les paris sont ouverts. Je suis
preneur de toute remarque/tout conseil :-)

Merci d'avance,
--
Vincent Jacques

"S'il n'y a pas de solution, c'est qu'il n'y a pas de problème."
Devise Shadok

4 réponses

Avatar
kanze
Vincent Jacques wrote:

est-ce que la norme dit que les variables membres statiques
sont initialisée avant les objets globaux?


Non, et la façon que tu as formulé la question me semble
indiquer une mécompréhension de base. Fondamentalement, la durée
de vie d'un objet ne dépend qu'indirectement de sa portée. Le
C++ reconnaît quatre durées de vies : statique, automatique,
dynamique et temporaire. Des objets déclarés à la portée
référentielle, des objets membre statiques, et des objets à
portée de bloc explicitement déclarés static ont une durée de
vie statique. (Aussi : chaque fois que je dis « objet » ici,
il faut s'entendre « objet ou référence ». À l'exception près
qu'il n'y a pas d'initialisation à zéro, les références
obéissent aux même règles que les objets.)

Quand à l'ordre de construction des objets à durée de vie
statique, le langage donne fort peu de garanties :

-- Les objets statiques à portée de bloc seront initialisés la
première fois que le flux d'exécution rencontre leur
définition.

-- Les objets statiques d'une même unité de compilation, autres
ceux à portée de bloc, seront initialisés dans l'ordre de
leur définition dans la source.

Il n'y a aucune règle qui différencie les objets à durée de vie
statique à portée de classe des ceux à portée référentielle.

Note que dans la pratique, même si la norme ne le garantit pas à
100%, les initialisations du deuxième point ci-dessus se font
avant l'entrée dans main.

Si oui, inutile de continuer, ça résout, je crois, mon
problème.

Sinon, ben euh je vais essayer d'être synthétique:

Mon application peut recevoir des données de plusieurs sources
(port série, port parallèle...) Mon utilisateur (moi, avec ma
casquette "Utilisateur") doit pouvoir choisir quelle source
utiliser. Je veux également pouvoir ajouter à mon application
un nouveau type de source sans modifier le code existant,
juste en ajoutant du code dans de nouveaux fichiers.

Pour ce faire, j'envisage la structure suivante:

Une classe virtuelle Source et un singleton (celui de ma
question d'hier, pour ceux qui suivent) SelecteurDeSource.

Le constructeur de Source enregistre chaque Source concrète
auprès du SelecteurDeSource. J'accède ensuite aux Sources
grace à lui.

Pour ne pas modifier le code existant, je mets la définition
et l'implémentation des Sources concrètes dans un fichier .cpp
et dans le même fichier je déclare une instance globale (de
porté le-dit fichier) de ce type.

Maintenant, le rapport avec la question du début de ce message:

si mes Sources sont initialisées avant la variable membre
statique SelecteurDeSource* SelecteurDeSource::m_Instance je
suis dans de beaux draps, puisque GetInstance() ne créera
(vraisemblablement, ie si m_Instance est non null) pas
l'instance et accèdera à de la mémoire non alouée.

Voili voilou, est-ce que je fais une erreur de conception,
est-ce que je ne connais pas encore assez le C++, les paris
sont ouverts. Je suis preneur de toute remarque/tout conseil
:-)


C'est un problème classique ; j'ai fait pareil souvent.

L'astuce, c'est d'utiliser un singleton pour le map (ton
"SelecteurDeSource").

Pendant qu'on y est, n'oublie pas qu'il ne suffit pas de mettre
les sources dans une bibliothèque ; des modules d'une
bibliothèque ne font partie du programme que si elles résoudent
au moins un externe. Il faut les spécifier explicitement en
tant que fichier objet au linker.

Aussi, note bien que cet idiome se prête à merveille à des
variations avec chargement dynamique des modules. Quand on
choisit une source, si on ne le trouve pas déjà présente dans le
map, on essaie de le charger dynamiquement, en appliquant une
convention de nommage pour trouver le fichier .dll/.so. Ce qui
résoud aussi ton problème d'ordre d'initialisation, parce que
les objets source ne seront chargés qu'à la démande, bien après
la construction du map.

--
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
Vincent Jacques
kanze wrote:
Vincent Jacques wrote:
est-ce que la norme dit que les variables membres statiques
sont initialisée avant les objets globaux?


Il n'y a aucune règle qui différencie les objets à durée de vie
statique à portée de classe des ceux à portée référentielle.


Merci pour ta réponse.

Mon application peut recevoir des données de plusieurs sources
(port série, port parallèle...) Mon utilisateur (moi, avec ma
casquette "Utilisateur") doit pouvoir choisir quelle source
utiliser. Je veux également pouvoir ajouter à mon application
un nouveau type de source sans modifier le code existant,
juste en ajoutant du code dans de nouveaux fichiers.


C'est un problème classique ; j'ai fait pareil souvent.
L'astuce, c'est d'utiliser un singleton pour le map (ton
"SelecteurDeSource").


Voui, ça c'est clair.

Je vais mettre ci-dessous un code minimal pour mieux expliquer mon souci.
J'ai regroupé certaines unités de compilation pour la lisibilité.

L'important, c'est que je n'ai pas modifié le fichier
"randomdatasource.cpp". C'est dans ce fichier que je déclare un objet
DataSource concret. Ça me permet de ne rien modifier si je veux ajouter
un autre fichier "serialportdatasource.cpp".

Mon problème est qu'ainsi je ne suis pas assuré que
DataSourceSelector::m_Instance est initialisé à NULL avant la création
(et donc l'enregistrement auprès de DataSourceSelector) des DataSource
concrets.

J'aime pas trop inonder le ng de code, mais là je m'en sortirai pas en
expliquant... Merci d'avance à ceux qui me liront et encore plus à ceux
qui me proposeront une solution :-)

Bon week-end.

// Fichier "datasource.h"

#include <vector>
#include <string>
#include <iostream>

class DataSource;

class DataSourceSelector // Singleton
{
public:
static DataSourceSelector* GetInstance()
{
return m_Instance==NULL ? m_Instance=new DataSourceSelector : m_Instance;
};
void Register(DataSource* ds)
{
m_RegistredSources.push_back(ds);
}
void ListAll(std::ostream& = std::cout) const;
private:
DataSourceSelector() {}; // Pas de création à l'extérieur de la classe.
static DataSourceSelector* m_Instance;
std::vector<DataSource*> m_RegistredSources;
};

class DataSource
{
protected:
DataSource() {DataSourceSelector::GetInstance()->Register(this);};
virtual ~DataSource() {};
public:
virtual std::string GetName() const = 0;
};

// Fin du fichier "datasource.h"

// Fichier "main.cpp"

#include <iostream>

#include "datasource.h"

DataSourceSelector* DataSourceSelector::m_Instance = NULL;

void DataSourceSelector::ListAll(std::ostream& s) const
{
for(std::vector<DataSource*>::const_iterator it =
m_RegistredSources.begin();
it != m_RegistredSources.end();
++it)
{
s << (*it)->GetName() << std::endl;
}
}

int main(int, char**)
{
DataSourceSelector::GetInstance()->ListAll();
return 0;
}

// Fin du fichier "main.cpp"

// Fichier "randomdatasource.cpp"

#include "datasource.h"

class RandomDataSource : public DataSource
{
public:
RandomDataSource() {};
~RandomDataSource() {};
std::string GetName() const { return "Générateur de nombres aléatoires"; };
};

RandomDataSource rds;

// Fin du fichier "randomdatasource.cpp"

--
Vincent Jacques

"S'il n'y a pas de solution, c'est qu'il n'y a pas de problème."
Devise Shadok


Avatar
James Kanze
Vincent Jacques wrote:
kanze wrote:
Vincent Jacques wrote:



[...]
Mon application peut recevoir des données de plusieurs sources
(port série, port parallèle...) Mon utilisateur (moi, avec ma
casquette "Utilisateur") doit pouvoir choisir quelle source
utiliser. Je veux également pouvoir ajouter à mon application
un nouveau type de source sans modifier le code existant,
juste en ajoutant du code dans de nouveaux fichiers.




C'est un problème classique ; j'ai fait pareil souvent.
L'astuce, c'est d'utiliser un singleton pour le map (ton
"SelecteurDeSource").



Voui, ça c'est clair.


Je vais mettre ci-dessous un code minimal pour mieux expliquer mon
souci. J'ai regroupé certaines unités de compilation pour la
lisibilité.


L'important, c'est que je n'ai pas modifié le fichier
"randomdatasource.cpp". C'est dans ce fichier que je déclare
un objet DataSource concret. Ça me permet de ne rien modifier
si je veux ajouter un autre fichier
"serialportdatasource.cpp".


Mon problème est qu'ainsi je ne suis pas assuré que
DataSourceSelector::m_Instance est initialisé à NULL avant la
création (et donc l'enregistrement auprès de
DataSourceSelector) des DataSource concrets.


Les initialisations à 0 ont lieu avant toute autre
initialisation. Ensuite, les initialisations statiques ont lieu
avant la première initialisation dynamique. La seule source
éventuelle de problèmes, ce sont des initialisations dynamiques.

Il y a beaucoup d'idiomes qui s'y basent. Y compris celui du
singleton.

--
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
Vincent Jacques
James Kanze a écrit dans fr.comp.lang.c++:
Mon problème est qu'ainsi je ne suis pas assuré que
DataSourceSelector::m_Instance est initialisé à NULL avant la
création (et donc l'enregistrement auprès de
DataSourceSelector) des DataSource concrets.


Les initialisations à 0 ont lieu avant toute autre
initialisation.


Ah donc ça, ça garantie le bon comportement de mon application. Merci encore
pour tes réponses.
--
Vincent Jacques

"S'il n'y a pas de solution, c'est qu'il n'y a pas de problème"
Devise Shadocks