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 :
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 element operator*() { const auto readAddress = sector_m.index() * SECTOR_SIZE + offset_m; return element{readAddress, flashDriver}; }
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.
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; } };
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 :
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 element* operator->() { const auto readAddress = sector_m.index() * SECTOR_SIZE + offset_m; static element local{}; local = element{readAddress, flashDriver}; return &local; }
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...
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';
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
Partager