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
    72
    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 : 72
    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 526
    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 526
    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
    762
    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 : 762
    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
    72
    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 : 72
    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

  5. #5
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    697
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 697
    Par défaut
    Salut à tous,

    Petite parenthèse d'abord, préfixer les noms de variable de tiret(s) bas, sauf cas particulier, fait définitivement partie des mauvaises pratiques.

    Cela dit, @sylvain1984, tu ne présentes qu'une vue assez sommaire de ta problématique. En l'état, et après une lecture en diagonale de QSettings (je ne connais pas Qt, ou que de nom), j'écrirais qu'un simple auto x= prefs.value<bool>("group/key"); devrait suffire.

    En fait, pour ton code, le type de transtypage doit être connu ou déduit à la compilation. C'est pour cette raison que tes QSettings sur base de variants ou unions like te font tourner en rond. Assurément, tu as un problème de design quelque part, et le peu que j'en vois préfigure déjà des bugs latents.

  6. #6
    Membre éclairé
    Avatar de sylvain1984
    Homme Profil pro
    Retraité, développeur amateur
    Inscrit en
    Juillet 2023
    Messages
    72
    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 : 72
    Par défaut
    Bonjour @kaitlyn,

    Merci pour ton retour !

    Citation Envoyé par kaitlyn Voir le message
    Petite parenthèse d'abord, préfixer les noms de variable de tiret(s) bas, sauf cas particulier, fait définitivement partie des mauvaises pratiques.
    Petite précision préalable : comme l'indique le texte sous mon avatar je suis un développeur du dimanche, à la retraite désormais, assez mordu mais pas pro car pas formé ad-hoc. Dans mon boulot j'ai développé et fait développer de petites appli intégrant des algos de RO. Mes connaissances m'ont tout de même permis de demander à une société d'informatique de changer le type d'un conteneur, ce qui a réduit les délais une fonction d'un facteur de 5.
    Comme j'ai plus de temps j'aime à apprendre au travers d'une appli pour une association. Je suis dessus depuis deux ans...
    Aussi je suis très heureux quand j'ai un retour comme les vôtres. Merci encore !


    Concernant le tiret bas devant les variables, j'ai pris cette habitude récemment pour distinguer les variables de la classe des variables locales aux fonctions de cette même classe. En quoi c'est une mauvaise pratique ?

    Citation Envoyé par kaitlyn Voir le message
    Cela dit, @sylvain1984, tu ne présentes qu'une vue assez sommaire de ta problématique. En l'état, et après une lecture en diagonale de QSettings (je ne connais pas Qt, ou que de nom), j'écrirais qu'un simple auto x= prefs.value<bool>("group/key"); devrait suffire.
    Oui, c'est la solution que j'ai finalement retenu. Je voulais mettre en œuvre un patron pour réduire le code. J'étais parti sur un truc trop compliqué, voire impossible. Mais j'ai trouvé plus simple avec la méthode postée le 11 dernier ci-dessus grâce à l'indication de @bacelar.

    Citation Envoyé par kaitlyn Voir le message
    En fait, pour ton code, le type de transtypage doit être connu ou déduit à la compilation. C'est pour cette raison que tes QSettings sur base de variants ou unions like te font tourner en rond. Assurément, tu as un problème de design quelque part, et le peu que j'en vois préfigure déjà des bugs latents.
    design... design... je ne te rassurerai pas en te disant qu'il n'y en a jamais eu pour mon soft... Comme on dit vulgairement dans mon boulot, je suis parti "vent du c*l dans la plaine", persuadé de plier ça en une semaine... Comme le dirait M Robert C. Martin, mon code doit sentir horriblement mauvais...
    Je manque en fait d'un langage de description comme UML. J'ai installé Modelio sur ma Debian et je caresse l'espoir d'y consacrer enfin le temps nécessaire quand le périmètre fonctionnel de l'appli sera couvert, ce qui ne saurait tarder !

    Bonne soirée !
    Sylvain

  7. #7
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 526
    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 526
    Par défaut
    Concernant le tiret bas devant les variables, j'ai pris cette habitude récemment pour distinguer les variables de la classe des variables locales aux fonctions de cette même classe. En quoi c'est une mauvaise pratique ?
    https://softwareengineering.stackexc...e-the-compiler
    Donc, vous risquez des emmerdes de oufs avec des compilateurs, au pire moment, comme d'hab.

    Plutôt que de prendre des conventions qui viennent d'autres langages qui ne matchent pas avec le C++, vous pouvez vous tourner vers une utilisation raisonnée de la notation hongroise :
    "m_..." pour des membres d'une classe
    "s_..." pour les statiques d'une classe
    "p_..." pour des paramètres (si nécessaire et surement pas pour des pointeurs)
    "l_..." pour une variable locale (même si je trouve cela un peu lourd et inutile).
    "g_..." beurk...

+ 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