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 :

Implementation correcte de l'opérateur -> quand les données de la collection ne sont disponibles en RAM


Sujet :

Langage C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut Implementation correcte de l'opérateur -> quand les données de la collection ne sont disponibles en RAM
    Hello !

    Je galère un peu à fournir un iterator à ma classe custom, avec une sémantique propre et sans vices cachés. Je vais essayer de résumer le code mais je ne pourrais pas fournir un code complet pour reproduire.

    Ma classe principale est FlashStorage. C'est une classe template qui permet de sauver des objets de type T dans une mémoire flash. Elle a donc une méthode store(const T& t). Lors de la sauvegarde, un UID et à un CRC sont associés à ce T, au sein d'une structure nommée StoredItem. Au final, la flash stocke donc une collection de StoredItem, et non directement de T.

    Mon objectif est de fournir une API à ma classe pour pouvoir faire des itérateurs lors de la relecture de la donnée. FlashStorage a donc une inner class iterator et des méthodes begin() et end(). Ca fonctionne bien. Je peux faire des range-based for loops, des std::find_if, des std::lower_bound (car ma collection est triée par essence), des accès abritaires (genre : flashStorage.begin() + 42).

    J'ai une contrainte importante : la recherche d'élement doit être rapide. Pour cela, iterator ne pointe pas directement sur la donnée, mais permet d'accéder via un proxy à la donnée. Ainsi, par exemple, FlashStorage::iterator::operator* ressemble à ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    element operator*() {
        const auto readAddress = sector_m.index() * SECTOR_SIZE + offset_m;
        return element{readAddress, flashDriver};
    }
    element est un proxy car il ne va réellement lire la donnée que si on le demande :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class element {
        [[nodiscard]] UID getUid() const {
            UID uid;
            /* --> lire un UID à l'adresse qui va bien */
            return uid;
        }
     
        [[nodiscard]] T getT() const {
            T t;
            /* --> lire un T à l'adresse qui va bien */
            return t;
        }
    };
    Ainsi, je peux parcourir très rapidement la mémoire flash à la recherche du bon UID, et lire le T associé seulement quand je l'ai trouvé. Jusque là, tout se passe comme je le souhaite.

    Toutefois, écrire (*it).getUid() ou (*it).getT() est assez peu élégant visuellement. On a envie d'écrire it->getUid() ou it->getT() à la place. Et c'est là que les difficultés arrivent. Je suppose qu'elles viennent du fait que les données dans la collection n'existent pas directement sous forme d'objets C++. Ainsi, comment implementer correctement un opérateur avec une telle signature :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    element* operator->() {
        return p /* avec p = ?! */;
    }
    ?

    J'ai tenté quelque chose comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    element* operator->() {
        const auto readAddress = sector_m.index() * SECTOR_SIZE + offset_m;
        static element local{};
        local = element{readAddress, flashDriver};
        return &local;
    }
    Ca permet de faire it->getUid() ou it->getT(). Mais il y a un "léger" hic, et il est illustré dans le code suivant :

    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
    std::cout << "\n\n\n" << '\n';
    auto it = flashStorage.begin();
     
    std::cout << "access" << '\n';
    auto avant = it.operator->(); // AIE... On garde l'adresse vers l'element statique
    std::cout << *avant << '\n';
     
    std::cout << "incr" << '\n';
    ++it;
    // l'iterateur cible desormais une autre adresse dans la memoire flash
     
    std::cout << *avant << '\n';
    std::cout << "access again" << '\n';
    auto apres = it.operator->(); // modifie l'element statique, vers lequel pointe 'avant'
     
    // 'avant' et 'apres' pointent sur la même chose : c'est OK pour 'apres' mais pas pour 'avant'
    std::cout << *avant << '\n';
    std::cout << *apres << '\n';
    A part mettre un commentaire comme // Don't save the value returned by this operator as it will probably become invalid (for instance if operator++ is called on the iterator) en documentation de mon operator->, je n'ai pas d'idée brillante...

    Qu'en pensez-vous ? Que feriez-vous ? Est-ce un cas trop limite pour s'en soucier ? Est-ce que le comportement dans un tel cas est normé ?

    Merci d'avance pour vos idées

    PS : je ne peux pas faire d'allocation dynamique de mémoire

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    À froid : réserve un petit buffer dans ton iterator que tu initialises avec ton proxy, puis tu peux retourner le proxy via ce buffer comme bon te semble ?
    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
    class Iterator
    {
      Iterator()
      {
        const auto readAddress = sector_m.index() * SECTOR_SIZE + offset_m;
        new (m_element.data()){readAddress, flashDriver};
      }
      ~Iterator()
      {
        get()->~element();
      }
     
      element& operator*() { return *get(); }
      element* operator->() { return get(); }
     
      element* get() { return reinterpret_cast<element*>(m_element.data()); }
     
    std::array<uint8_t, sizeof(element)> m_element;
    };
    Bien sûr tout est valide tant que l'itérateur existe uniquement. Mais ça, ce n'est pas un cas particulier à cette implémentation il me semble.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 565
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 565
    Points : 7 648
    Points
    7 648
    Par défaut
    Bonjour,

    Et en retournant directement un élément qui aurait son operator->()
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    auto  operator->() {
        const auto  readAddress = sector_m.index() * SECTOR_SIZE + offset_m;
        struct selectable_element {
            element   elem;
            element*  operator->() {
                return  &elem;
            }
        };
        selectable_element  local{element{readAddress, flashDriver}};
        return  local;
    }

  4. #4
    Membre émérite
    Avatar de skeud
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2011
    Messages
    1 091
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 1 091
    Points : 2 724
    Points
    2 724
    Billets dans le blog
    1
    Par défaut
    Je pense que le problème vient du fait que tu veuilles retourner un type element*

    quand tu regarde dans la stl pour la classe list::iterator

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    	reference operator*() const
    		{	// return designated value
    		return ((reference)**(_Mybase *)this);
    		}
     
    	pointer operator->() const
    		{	// return pointer to class object
    		return (pointer_traits<pointer>::pointer_to(**this));
    		}
    Il semblerait qu'il y a un operator* qui permet de retourner une référence sur un type reçu en template.
    Et que operator-> fait appel à operator* qui permet de faire le même taf que toi.

    As-tu essayer:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    element operator->() const
    {
     return (*this);
    }
    Pas de solution, pas de probleme

    Une réponse utile (ou +1) ->
    Une réponse inutile ou pas d'accord -> et expliquer pourquoi
    Une réponse à votre question


  5. #5
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    La solution de dalfab était la bonne ! Merci !

    @bousk: ta solution ressemble au final beaucoup à mon static j'ai l'impression. En tout cas, elle semble avoir les mêmes défauts, à savoir que quelqu'un peut accéder à l'élément, garder l'adresse vers la donnée interne, fait bouger l'itérateur et refait accès, invalidant ainsi son premier pointeur.

    @skeud: il faut renvoyer un pointer, ou alors (et c'est la solution de dalfab) quelque chose qui a lui-même un operateur -> (c'est le drill-down behavior https://stackoverflow.com/a/2470586/12342718). Ta solution produit l'erreur suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    D:\gitlab-flashstorage\source\main.cpp:22:18: error: result of 'operator->()' yields non-pointer result
       22 |     dummy.begin()->getT();

  6. #6
    Membre émérite
    Avatar de skeud
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2011
    Messages
    1 091
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 1 091
    Points : 2 724
    Points
    2 724
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Bktero Voir le message

    @skeud: il faut renvoyer un pointer, ou alors (et c'est la solution de dalfab) quelque chose qui a lui-même un operateur -> (c'est le drill-down behavior https://stackoverflow.com/a/2470586/12342718). Ta solution produit l'erreur suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    D:\gitlab-flashstorage\source\main.cpp:22:18: error: result of 'operator->()' yields non-pointer result
       22 |     dummy.begin()->getT();
    Ok super, merci pour les infos et le retour
    Pas de solution, pas de probleme

    Une réponse utile (ou +1) ->
    Une réponse inutile ou pas d'accord -> et expliquer pourquoi
    Une réponse à votre question


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

Discussions similaires

  1. Réponses: 0
    Dernier message: 16/05/2018, 11h21
  2. Les présentations de Google I/O sont disponibles en ligne
    Par le y@m's dans le forum Actualités
    Réponses: 1
    Dernier message: 08/06/2009, 20h02
  3. Réponses: 5
    Dernier message: 22/10/2008, 17h40
  4. Réponses: 1
    Dernier message: 31/10/2007, 21h43

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