OVH Cloud OVH Cloud

[Debutant] prefs, macros et singleton

10 réponses
Avatar
tibug
Bonjour,

je commence à écrire une petite application "à but didactif" pour
essayer de progresser un peu en c++

après avoir lu les threads sur vars globales, j'ai placé toutes les
prefs utilisateur dans une classe PrefsManager qui hérite d'une
classe Singleton.
PrefsManager stocke une multitude de variables privées, les initialise
à partir d'un fichier ini (multiples readIni()) et les y sauvegarde
(multiples writeIni()) + toutes les méthodes GetMavar() SetMavar()
qui vont bien et qui alourdissent l'accès aux variables.

bref je suis immédiatement tenté de retomber dans le bidouillage
pour allèger le tout :-(

Pouvez-vous me dire si les constructions suivantes sont "passables",
à éviter, proscrire... les problèmes (maintenance ou autre) qui
peuvent apparaître en les utilisant

1) une instance globale de PrefsManager et accèder au variables :

static PrefsManager *PrefsMgr;
#define MYVAR PrefsMgr->GetMyVar()

(un autre avantage étant peut-être de pouvoir facilement par la
suite réattribuer la gestion d'une partie des prefs à autre objet)

2) idem avec un namespace :

#define MYVAR MyAppNS::nsGetMyVar()

namespace MyAppNS {
static PrefsManager *PrefsMgr;
int GetMyVar(); // return PrefsMgr->GetMyVar();
}

3) un enum en plus et des tableaux de valeurs pour les prefs dans
PrefsManager, pour permettre également des itérations pour
l'initialisation et la sauvegarde des préférences

enum { MYVARidx, ...}

#define MYVAR MyAppNS::nsGetPref(MYVARidx)

namespace MyAppNS {
static PrefsManager *PrefsMgr;
int GetPref(int i); // return PrefsMgr->GetPref(i);
bool GetPref(int i); // return PrefsMgr->GetPref(i);
...
}

int PrefsManager::GetPref(int idx) {return prefs_int[idx];}
bool PrefsManager::GetPref(int idx) {return prefs_bool[idx];}


4) Autre chose qui ferait ça bien mieux ?


5) static PrefsManager *PrefsMgr;
#define GetThePref(x) PrefsMgr->GetPref(\"#x\")
avec prefs stockées dans hash maps du PrefsManager
(parceque pour débugger c'est plus rigolo ;-)))


Merci de votre aide

tibug

10 réponses

Avatar
Fabien LE LEZ
On Sun, 13 Mar 2005 00:13:46 +0100, tibug <tibug29_AROBASE_yahoo_fr>:

static PrefsManager *PrefsMgr;


La méthode classique pour faire un singleton est quelque chose comme :

// .h

class PrefsManager
{
public:
static PrefsManager& GetInstance();

int& MaVariable() { return ma_variable; } // ceci servira plus bas
int const& MaConstante() const { return ma_constante; } // idem

private:
PrefsManager();
static PrefsManager *ptr_instance;

int ma_constante; // ceci servira plus bas
int ma_variable; // idem
};


// .cpp

PrefsManager* PrefsManager::ptr_instance= NULL;
PrefsManager& PrefsManager::GetInstance()
{
if (ptr_instance == NULL) ptr_instance= new PrefsManager;
return *ptr_instance;
}

#define MYVAR PrefsMgr->GetMyVar()


Franchement, c'est encore pire qu'une variable globale.

Si certaines "fausses variables globales" te servent souvent, tu peux
en faire des fonctions libres :

// Version "lecture seule" :

// .h

int const& MaConstante();
int& MaVariable();

// .cpp

int const& MaConstante()
{
return PrefsManager::GetInstance().MaConstante();
}

int& MaVariable()
{
return PrefsManager::GetInstance().MaVariable();
}

--
;-)

Avatar
Loïc Joly

La méthode classique pour faire un singleton est quelque chose comme :

// .h

class PrefsManager
{
public:
static PrefsManager& GetInstance();

int& MaVariable() { return ma_variable; } // ceci servira plus bas
int const& MaConstante() const { return ma_constante; } // idem

private:
PrefsManager();
static PrefsManager *ptr_instance;

int ma_constante; // ceci servira plus bas
int ma_variable; // idem
};


// .cpp

PrefsManager* PrefsManager::ptr_instance= NULL;
PrefsManager& PrefsManager::GetInstance()
{
if (ptr_instance == NULL) ptr_instance= new PrefsManager;
return *ptr_instance;
}


C'est UNE méthode classique. Une autre existe qui est aussi assez
utilisée (plus simple à écrire, l'objet est détruit à la fin du programme) :

class PrefsManager
{
public:
static PrefsManager& instance();

private:
PrefsManager();
};

PrefsManager& PrefsManager::instance()
{
static PrefsManager theInstance;
return theInstance;
}


--
Loïc

Avatar
Fabien LE LEZ
On Sun, 13 Mar 2005 09:46:56 +0100, Loïc Joly
:

l'objet est détruit à la fin du programme


Quand, exactement ?
N'y a-t-il pas le risque que la fonction soit appelée après la
destruction de l'objet ?


--
;-)

Avatar
Loïc Joly
On Sun, 13 Mar 2005 09:46:56 +0100, Loïc Joly
:


l'objet est détruit à la fin du programme



Quand, exactement ?
N'y a-t-il pas le risque que la fonction soit appelée après la
destruction de l'objet ?


Ca doit être possible, à condition d'avoir une variable globale d'un
type ne faisant pas apper à instance() pendant sa construction, mais y
faisant appel pendant sa destruction.

Si ces conditions ne sont pas remplies, alors ta solution convient mieux
à la situation.

--
Loïc


Avatar
Arnaud Debaene
Fabien LE LEZ wrote:
On Sun, 13 Mar 2005 09:46:56 +0100, Loïc Joly
:

l'objet est détruit à la fin du programme


Quand, exactement ?
A la fin du programme, après le retour du main. Les objets à durée de vie

statique sont alors détruit dans l'ordre inverse de leur création (sachant
que cet objet est créé lors du 1er appel à la méthode instance).

N'y a-t-il pas le risque que la fonction soit appelée après la
destruction de l'objet ?
On peut imaginer un problème si on appelle cette méthode dans le destructeur

d'un autre objet à durée de vie statique qui serait détruit après celui-ci :
un cas quand même assez tordu. Il y a également peut-être un problème si on
fait mu-muse avec atexit, mais je n'en suis pas sûr.

Arnaud


Avatar
tibug


[code pour une classe Singleton]





Merci à vous, en fait j'ai déjà ma classe Singleton (template) et une
classe PrefsManager qui en hérite. J'ai mis le code à la fin du message
plus bas si ça peut servir à d'autres débutants.

pour l'instant j'en suis à la phase 0 des petits tests préalables...
bloqué au chapitre 0 de "comment gérer les prefs globales facilement" :(

j'ai quelques idées qui fonctionnent (compilent) mais je ne sais pas si
ça tient de la bidouille (ou hypotétiquement si c'est des constructions
"passables" -- j'aimerais faire "propre" puisque j'écris cette app pour
apprendre) d'où mon précédent message.

je vais essayer de préciser un peu (pour les gens pressés, aller
directement au 3 :)


1) dans un premier temps, je voudrai alléger l'accès l'écriture des
accès aux variables du PrefsManager dans tout le programme, c'est à
dire éviter d'écrire partout :

PrefsManager *pmgr;
int truc = pmgr->GetPrefTruc(); // un GetPref...() par var

mais plutôt, n'avoir à écrire que :

int truc = TRUC;
// TRUC: sorte de variable en "lecture seule" (mais pas constante)
// que seule une classe héritant de PrefManager peut modifier
// (ce qui permet de se passer de tous les ->SetTruc(valeur))

après avoir défini quelque chose qui fasse la même que ce qui suit
(mais en plus "propre" donc) :

-- monAppPrefs.h -------

namespace monAppPrefs {
static PrefsManager *m_PrefsMgr;
int GetPrefTruc();
bool GetPrefMachin();
}

#define TRUC monAppPrefs::nsGetPrefTruc()
#define MACHIN monAppPrefs::nsGetPrefMachin()

-- monAppPrefs.cpp -------

int monAppPrefs::nsGetPrefTruc() {
return m_PrefsMgr->GetPrefTruc();
}


2) (et/ou) essayer de généraliser la forme des accès aux variables
en utilisant des méthodes "génériques" et un container dans
PrefManager qui me permette d'itérer lors de l'initialisation
et la sauvegarde des préférences utilisateur :

=> la solution ci-dessous me semble bien trop coûteuse en CPU
à cause des itérations à chaque accès à une préférence


-- monAppPrefs.h -------

namespace monAppPrefs {
static PrefsManager *m_PrefsMgr;
int nsGetIntPref(const std::string& str);
bool nsGetBoolPref(const std::string& str);
... // une méthode par type de pref
}

#define TRUC monAppPrefs::nsGetIntPref("truc")
#define MACHIN monAppPrefs::nsGetBoolPref("machin")

-- monAppPrefs.cpp -------

int monAppPrefs::nsGetIntPref(const std::string& str) {
return m_PrefsMgr->GetIntPref(str);
}
-------

et dans et dans PrefsManager des containers style hash_map
(un par type de pref aussi) pour bien faire ramer le tout ;-(

int PrefsManager::GetIntPref(const std::string& str) {
return m_hmiPref(str); // retourne l'int associé à str
}

int PrefsManager::GetBoolPref(const std::string& str) {
return m_hmbPref(str); // retourne le booléen associé à str
}


3)

Au final j'aimerai trouver une solution pour que lorsque
j'ajoute/retire une variable à PrefManager, il suffise de
modifier uniquement les #define.

Exemple (le premier qui me vient): un jour lointain j'ajoute
un "SkinManager" aussi de type Singleton à qui je délègue
la (pseudo) variable "truc" et je fixe "machin" :

namespace monAppNS { // pas sûr de la syntaxe
namespace monAppPrefs {...}
}
// en donnant spécifaint une valeur par défaut 100 pour "truc"
#define TRUC monAppNS::monAppPrefs::nsGetIntPref("truc",100)
#define MACHIN monAppNS::monAppPrefs::nsGetIntPref("machin",true)

devient:

namespace monAppNS {
namespace monAppPrefs {...}
namespace monAppSkins {...}
}
#define TRUC monAppNS::monAppSkins::nsGetIntPref("truc",100)
#define MACHIN true


Merci d'avoir eut le courage de tout lire (?! ;-) )

A+

tibug



=== core_Singleton.h ========
#ifndef CORE_SINGLETON_H
#define CORE_SINGLETON_H

template <typename T> class Singleton {

public :
static T* GetInstance() {
if(!m_pInstance) m_pInstance = new T;
return ((T *)m_pInstance);
}
static void Destroy() {
if(m_pInstance) delete m_pInstance;
m_pInstance = NULL;
}

protected :
Singleton() {}
~Singleton() {}

private :
static T* m_pInstance;
Singleton(Singleton&);
void operator =(Singleton&);
};

template <class T> T *Singleton<T>::m_pInstance = NULL;

#endif // ifndef CORE_CSINGLETON_H


=== core_PrefManager.h ========
#ifndef CORE_SINGLETON_H
#include "core_Singleton.h"
#endif CORE_SINGLETON_H

#ifndef CORE_PREFSMANAGER_H
#define CORE_PREFSMANAGER_H

class PrefsManager : public Singleton<PrefsManager> {

friend class Singleton<PrefsManager>;

private :

bool m_pInitialised;
int m_iTruc;
// UNMAX de préférences untilisateur ici :-(

PrefsManager();
~PrefsManager();

//... un ReadIni/WriteIni par type (en fait heritees de IniFile)
int ReadIni(const std::string& key, int valDef);
int WrtiteIni(const std::string& key, int newVal);
bool ReadIni(const std::string& key, bool valDef);
bool WrtiteIni(const std::string& key, bool newVal);

public :
// tous les couples set/get
void SetPrefTruc(int val);
int GetPrefTruc();
};

#endif // ifndef CORE_PREFSMANAGER_H


=== core_PrefManager.cpp ========
#ifndef CORE_PREFSMANAGER_H
#include "core_PrefsManager.h"
#endif CORE_PREFSMANAGER_H


// constructeur
PrefsManager::PrefsManager() {
if(!m_pInitialised) m_pInitialised = Initialise();
// peut-etre faire autrement
}

PrefsManager::~PrefsManager() {} // destructeur

bool PrefsManager::Initialise() {
// initialise toutes les variables en les lisant dans un
// fichier ini (ou avec une valeur par defaut si absente)
// UNMAXDE appels a ReadIni("maVar", valDefault) ici :-((
return res; // résultat de l'initialisation
}

int PrefsManager::SaveAllPrefs() {
// sauve toutes les prefs dans un fichier ini
// UNMAXDE appels à WriteIni("maVar", valUser) ici :-((
return res; // résultat de la sauvegarde
}

// UNMAXDE de GetPrefMachin
int PrefsManager::GetPrefTruc() { return m_iTruc; }

// et pour finir UNMAXDE de SetPrefMachin (eventuellement)
void PrefsManager::SetPrefTruc(int newVal) { m_iTruc = newVal; }


// Methodes pour lire/ecrire dans le fichier prefs.ini

int PrefsManager::ReadIni(const std::string& key, int valDef) {...}
bool PrefsManager::ReadIni(const std::string& key, bool valDef) {...}
//...

int PrefsManager::WrtiteIni(const std::string& key, int newVal) {...}
bool PrefsManager::WrtiteIni(const std::string& key, bool newVal) {...}
//...

Avatar
Fabien LE LEZ
On Sun, 13 Mar 2005 13:27:38 +0100, tibug <tibug29_AROBASE_yahoo_fr>:

mais plutôt, n'avoir à écrire que :

int truc = TRUC;


Je te propose, dans mon message de ce matin,

int truc= Truc();

Deux caractères de plus que ce que tu espères, mais c'est déjà ça...


--
;-)

Avatar
Samuel Krempp
le Sunday 13 March 2005 15:49, écrivit :

On Sun, 13 Mar 2005 13:27:38 +0100, tibug <tibug29_AROBASE_yahoo_fr>:

mais plutôt, n'avoir à écrire que :

int truc = TRUC;


Je te propose, dans mon message de ce matin,

int truc= Truc();

Deux caractères de plus que ce que tu espères, mais c'est déjà ça...


en fait, 7 caracs de moins :
Truc();
;-)

à priori je ne dupliquerais la var globale en une locale que si le nom est
vraiment long et qu'elle vas servir 10 fois ou +, ou encore que je vais
faire des modifs dessus, etc..


--
Sam


Avatar
Samuel Krempp
le Sunday 13 March 2005 09:46, écrivit :

C'est UNE méthode classique. Une autre existe qui est aussi assez
utilisée (plus simple à écrire, l'objet est détruit à la fin du programme)


j'aime assez ce système, je l'utilise bcp.

Dans l'optique de constantes et variables globales, je trouve qu'un
namespace est plus pratique qu'une classe pour permettre un accès rapide là
où on les utilise intensivement.

namespace MesConstantes {

const int ageDuCapitaine() { return 77; }

double& vitesseDuVent() {
static double v = 0;
return v;
}

}

...

void foo() {
using namespace MesConstantes;
double x = vitesseDuVent() * exp(ageDuCapitaine()/20.0);
vitesseDuVent() = x / 2;
...

mais bon, je suis pas certain du genre de "Prefs" dont il est question et si
ce genre de namespace s'y preterait, j'ai eu la flemme de lire en entier
les messages du O.P..

--
Sam

Avatar
tibug
Bonjour,


le Sunday 13 March 2005 15:49, écrivit :

Je te propose, dans mon message de ce matin,

int truc= Truc();

Deux caractères de plus que ce que tu espères, mais c'est déjà ça...



bah oui, ton message était pourtant bien clair, il va juste falloir que
je réapprenne à lire ;-( mea culpa

à priori je ne dupliquerais la var globale en une locale que si le nom est
vraiment long et qu'elle vas servir 10 fois ou +, ou encore que je vais
faire des modifs dessus, etc..
[+ réponse avec namespace]


Oui, en fait je compte regrouper ce genre de variables dans un
namespace+singleton pour y accèder lors de l'initialisation / mise à
jour et utiliser des copies locales dans les objets les utilisant.
(en fait le singleton charge/décharge/recharge les données à la demande)

Pour mon histoire de variable globales en lecture seule j'en suis là:
[cf. autre post "namespaces et importation de classes/namespaces"]

- Une classe PrefsServer sur modèle singleton : elle contient toutes les
variables en 'protected' et les méthodes publiques 'Gets()' associées.

- Une classe PrefsManager ('PrefsServer friendly') qui peut modifier les
variables protegées de PrefsServer (utilisée par exemple pour une
boîte de dialogue pour modifier les prefs).

et j'utilise un namespace MyAppN :

#include "PrefsServer.h"
#include "AutreNamespace.h"

namespace MyAppN {

using AutreNamespace; // je voudrais l'importer tout en le rendant
// "privé" sans avoir à l'implémenter ici et
// qu'on ne puisse donc pas y accèder
// autrement que par MyApp::AutreNamespace::...

class A {...}; // idem, (les tests avec extern)

int GetPrefsB() { return PrefsServer::GetInstance()->GetPrefsB(); };
}

A+

tibug