IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Collection d'objets de types différents ?


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Avatar de sylvain1984
    Homme Profil pro
    Retraité, développeur amateur
    Inscrit en
    Juillet 2023
    Messages
    71
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : Retraité, développeur amateur

    Informations forums :
    Inscription : Juillet 2023
    Messages : 71
    Par défaut Collection d'objets de types différents ?
    Bonjour,

    J'utilise QT 6.4 et en particulier sa classe QSettings qui offre une gestion d'un fichier de configuration assez simple.
    Mais je trouve un peu dommage, à chaque fois que je souhaite accéder à une variable, de devoir faire la chose suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    QSetting _preferences;
     
    // Pour lire une préférence
    _preferences.beginGroup("MonGroupeDePreferences");
         bool b = _preferences.value("UneCle").toBool(); // value() retourne un QVariant qu'il faut typer chaque préférence, du type :
    _preferences.endGroup();
     
    // Pour écrire une préférence :
    _preferences.beginGroup("MonGroupeDePreferences");
         _preferences.setValue("UneCle",bool value);
    _preferences.endGroup();
    C'est certainement beaucoup mieux que rien, mais à la longue ça agace un peu.

    J'ai donc pensé à encapsuler un QSetting par une classe contenant des get/set pour chaque préférence, plus quelques fonctions de maintenance. Mais je me retrouve avec une collection de fonctions dupliquant énormément de code. Par exemple, voici un get :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    bool MyPrefs::getModeDemo(){
        bool b = false;
        _preferences.beginGroup(grpApp);
        QStringList keys = _preferences.childKeys();
        if (keys.contains(modeDemoKey)){
            b = _preferences.value(modeDemoKey).toBool();
        }else{
            _preferences.setValue(modeDemoKey,modeDemoVal);
            b = modeDemoVal;
        }
        _preferences.endGroup();
        return b;
    }
    De base, pour accéder à une préférence, il faut le nom de sa clé et son type. J'ai alors pensé à créer une classe template pour stocker chaque préférence :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    template<class T>
    class OnePref {
    public:
        OnePref()                   = delete;
        OnePref(OnePref &t)         = delete;
        OnePref operator=(OnePref)  = delete;
     
        OnePref(QSettings * prefs, QString grp, QString cle, T valDefaut)  {
            set_prefs_obj(prefs);
            _groupe = grp;
            _cle = cle;
            _valeur_defaut = valDefaut;
        }
        ~OnePref(){};
     
        T get_valeur() {
            QVariant v = get_value_as_QVar();
            if (v.canConvert<T>(v)) return v.value<T>();
            else v.clear();
        }
    };
    L'idée est ensuite de stocker toutes les préférences comme des pointeur vers ces objets patron dans une Map dont la clé est celle de la préférence. Je sais, ça duplique la clé de la préférence, mais je crois que c'est rapide...
    Mais le compilateur n'est logiquement pas ok : les pointeurs ne pointent pas vers un même type.

    D'où l'idée ensuite de créer une classe virtuelle de laquelle hérite les objets préférence, puis de placer un pointeur vers cette classe virtuelle dans la Map.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class AbstractPref {
    public:
        AbstractPref():__preferences(nullptr){};
        virtual ~AbstractPref(){};
        void set_prefs_obj(QSettings * p) { if (p) __preferences = p; }
     
        QString     get_groupe()        const { return _groupe;       }
        QString     get_cle()           const { return _cle;          }
        QSettings * get_pref_obj()      const { return __preferences; }
        QVariant    get_value_as_QVar()             const;
        void        set_value_as_QVar(QVariant &v)  const;
     
        QSettings * __preferences;
        QString     _groupe;
        QString     _cle;
        QVariant    _valeur_defaut;
    };
     
    class OnePref  : public AbstractPref { /*..*/ };
     
    class MyNewPrefs {
    public:   MyNewPrefs();
        ~MyNewPrefs();
     
        void ajouter_preferences(AbstractPref * p) { _all_prefs[p->get_cle()] = p; }
     
    private:
        void set_prefs();
        QSettings                   _preferences;
        QMap<QString,AbstractPref*> _all_prefs;
    };
    Ça fonctionne. Sauf que pour accéder à une préférence il va me falloir transtyper _all_prefs["UneCle"] (ligne 30) qui renvoi un AbstractPref*, ce qui n'est pas dans l'esprit de ce que je souhaite faire.

    Typiquement, j'aimerais pouvoir faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    bool b = _all_prefs["UneCleBool"].value();
    J'ai relu mon Stroustrup sur l'héritage et les classes patrons, sans avancer vraiment.

    J'envisage d'ajouter dans la classe virtuelle une variable par type utilisé et un indice vers le type à utiliser...

    Est-ce que c'est possible ce genre de chose ?

    Sylvain

  2. #2
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 524
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 524
    Par défaut
    J'ai du mal à comprendre votre démarche de "customisation" de la classe "MyPrefs".

    Si c'est pour factoriser du code, pourquoi ne pas le faire sous forme de fonctions privées ?

    Je pense qu'une classe type "MyPrefs" n'est pas qu'un accesseur à fichier de conf. :
    - mécanisme de compatibilité ascendante
    - mécanisme de "failback"
    - etc...

    Donc templetiser complètement le traitement interne de ce type de classe ne me parait pas un "move naturel".

    J'ai l'impression que vous voulez transformer votre "MyPrefs" en une simple QMap++.

  3. #3
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 761
    Par défaut
    Citation Envoyé par sylvain1984 Voir le message
    Typiquement, j'aimerais pouvoir faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    bool b = _all_prefs["UneCleBool"].value();
    Je pense que le problème de base est là: vouloir un type différent en sortie sans avoir un type différent en entrée.

    Plutôt que de manipuler une clef sous forme de string, il faudrait plutôt une clef qui regroupe type et nom. Quelque chose comme cela:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    template<class T>
    struct PropertyTraits { static_assert(!sizeof(T), "missing specialization on T"); };
     
    // implémentation pour le type bool
    template<>
    struct PropertyTraits<bool>
    {
       static bool get(QVariant var)
      {
        return var.toBool(p);
      }
     
      static void set(QSettings & settings, QStringView key, bool value)
      {
        settings.setValue(key, value);
      }
    };
     
    // code commun pour tous les types
    template<class T, class Traits = PropertyTraits<T>>
    struct Property
    {
      QStringView key;
      T (*defaultValueMaker)() = []{ return T{}; };
     
      T read(QSettings const & settings) const
      {
        return Traits::get(settings.value(key));
      }
     
      template<class ValueMaker>
      T readOr(QSettings const & settings ValueMaker && valueMaker) const
      {
        // À voir si pour toi utiliser QVariant::canConvert() est mieux.
        if (settings.contains(key)) {
          return Traits::get(settings.value(key));
        }
        return std::forward<ValueMaker>(valueMaker)();
      }
     
      T readOrDefault(QSettings const & settings) const
      {
        return readOr(settings, defaultValueMaker);
      }
     
      // si tous les types map ceux de settings, on peut se contenter de settings.setValue(key, value);
      // mais le trait à l'avantage de gérer les types qui seraient incompatibles avec QVariant, par exemple: std::chrono::seconds.
      void write(QSettings const & settings, T && value) const
      {
        return Traits::set(settings, key, std::move(value));
      }
     
      // On pourrait n'avoir qu'une version de write() qui prend un `T`, mais cela fait une copie qui peut être évité dans le cas de QString.
      // On pourrait aussi prendre un `U&&` et forward() vers Traits::set() qui a l'avantage de déplacer la transformation du type dans le trait.
      void write(QSettings const & settings, T const& value) const
      {
        return Traits::set(settings, key, value);
      }
    };
     
    template<auto v>
    inline constexpr defaultValue = []{ return v; };
    Et qui s'utiliserait ainsi:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    inline constexpr Property<bool> uneClefDeBool { u"groupe/clef" };
    // ou en spécifiant une valeur par défaut
    inline constexpr Property<bool> uneClefDeBool { u"groupe/clef", defaultValue<true> };
    // ou (forme obligée pour QString / QStringLiteral qui ne sont pas des types constexpr ou structurel)
    inline constexpr Property<bool> uneClefDeBool { u"groupe/clef", []{ return true; } };
     
    bool b = uneClefDeBool.read(settings);
    uneClefDeBool.write(settings, false);
    je n'ai pas testé, mais il y a les grandes lignes. Après tu peux faire un wrapper sur QSettings si la forme clef.function(settings) dérange et que tu préfères avoir settings.function(clef).

  4. #4
    Membre éclairé
    Avatar de sylvain1984
    Homme Profil pro
    Retraité, développeur amateur
    Inscrit en
    Juillet 2023
    Messages
    71
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : Retraité, développeur amateur

    Informations forums :
    Inscription : Juillet 2023
    Messages : 71
    Par défaut
    Bonjour,

    Merci beaucoup pour vos réponses.

    Après avoir lu votre solution @bacelar, (avant un gros dodo...) j'ai effectivement testé avec bonheur la fonction template privée :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    template<class T> T get_pref(QString const & key){
            T val;
            _preferences.beginGroup(whichGrp[key]);
            if (_preferences.childKeys().contains(key)){
                val = _preferences.value(key).value<T>();
            }else{
                _preferences.setValue(key, whichdefault[key]);
                val = whichdefault[key].value<T>();
            }
            _preferences.endGroup();
            return val;
        }
        template<class T> void set_pref(QString const & key, T const & val){
            _preferences.beginGroup(whichGrp[key]);
                val = _preferences.setValue(key,val);
            _preferences.endGroup();
        }
    Cela simplifie en effet énormément le code que je vais pouvoir basculer dans l'entête :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    bool MyPrefs::getNomMin(){
        return get_pref<bool>(nomMinKey);
    }
    whichGrp[key] et whichDefaultGrp[key] sont deux QMap liants la clé à, respectivement, son groupe et à sa valeur par défaut (un QVariant).
    Ça à l'air de fonctionner, je n'ai pas encore migré en totalité.

    Quant à la solution de @jo_link_noir, je vais la digérer tranquillement.

    Merci encore,
    Sylvain

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 10
    Dernier message: 31/08/2015, 08h19
  2. Comment faire de la defensive copie avec un objet de type Collection
    Par l_informaticien dans le forum Collection et Stream
    Réponses: 12
    Dernier message: 20/01/2013, 21h56
  3. Réponses: 7
    Dernier message: 26/04/2011, 18h00
  4. Réponses: 4
    Dernier message: 23/02/2010, 16h33
  5. [VB6] Sauvegarder une collection d'objets
    Par Sayagh dans le forum VB 6 et antérieur
    Réponses: 7
    Dernier message: 19/09/2003, 11h58

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo