J’ai une classe de modèle AsIterator
qui prend un type de type numérique, dans cet exemple, juste un int
et le convertit en un iterator ( ++
et --
incrémente et décrémente le nombre, et operator*
renvoie simplement une référence).
Cela fonctionne bien sauf si elle est enveloppée dans un std::reverse_iterator
et compilée avec une optimisation quelconque ( -O
est suffisant). Lorsque j’optimise le binary, le compilateur supprime l’appel de déréférencement à reverse_iterator
et le remplace par une valeur étrange. Il faut noter qu’il fait toujours le bon nombre d’itérations . C’est juste la valeur obtenue par un iterator inversé qui est une erreur.
Considérez le code suivant:
#include #include template class AsIterator : public std::iterator { T v; public: AsIterator(const T & init) : v(init) {} T &operator*() { return v; } AsIterator &operator++() { ++v; return *this; } AsIterator operator++(int) { AsIterator copy(*this); ++(*this); return copy; } AsIterator &operator--() { --v; return *this; } AsIterator operator--(int) { AsIterator copy(*this); --(*this); return copy; } bool operator!=(const AsIterator &other) const {return v != other.v;} bool operator==(const AsIterator &other) const {return v == other.v;} }; typedef std::reverse_iterator<AsIterator> ReverseIt; int main() { int a = 0, b = 0; printf("Insert two integers: "); scanf("%d %d", &a, &b); if (b < a) std::swap(a, b); AsIterator real_begin(a); AsIterator real_end(b); for (ReverseIt rev_it(real_end); rev_it != ReverseIt(real_begin); ++rev_it) { printf("%d\n", *rev_it); } return 0; }
Cela devrait supposer de passer du plus grand nombre inséré au plus bas et de les imprimer, comme dans cette exécution (compilé avec -O0
):
Insert two integers: 1 4 3 2 1
Ce que je reçois avec -O
est à la place:
Insert two integers: 1 4 1 0 0
Vous pouvez l’ essayer en ligne ici ; les nombres peuvent varier mais ils sont toujours “erronés” lors de l’optimisation du binary.
Ce que j’ai essayé:
const
(c’est-à-dire renvoyer const int &
, et déclarer toutes les variables en tant que telles) ne le répare pas; reverse_iterator
de la même manière, par exemple, std::vector
fonctionne correctement; AsIterator
pour une boucle normale avant ou arrière, cela fonctionne bien. 0
qui est imprimée est réellement codée en dur par le compilateur, les appels à printf
ressemblent tous à ceci quand ils sont compilés avec -S -O
: movl $.L.str.2, %edi # .L.str.2 is "%d\n" xorl %eax, %eax callq printf
Étant donné la cohérence du comportement de clang et de gcc , je suis presque sûr qu’ils le font bien et que j’ai mal compris, mais je ne le vois vraiment pas.
Regarder l’ std::reverse_iterator
de libstdc ++ de std::reverse_iterator
révèle quelque chose d’intéressant:
/** * @return A reference to the value at @c --current * * This requires that @c --current is dereferenceable. * * @warning This implementation requires that for an iterator of the * underlying iterator type, @cx, a reference obtained by * @c *x remains valid after @cx has been modified or * destroyed. This is a bug: http://gcc.gnu.org/PR51823 */ _GLIBCXX17_CONSTEXPR reference operator*() const { _Iterator __tmp = current; return *--__tmp; }
La section @warning
indique qu’une condition du type d’iterator sous-jacent est que *x
doit restr valide même après la modification / destruction de l’iterator sous-jacent.
En regardant le lien de bogue mentionné, on découvre des informations plus intéressantes:
à un moment donné entre C ++ 03 et C ++ 11, la définition de reverse_iterator :: operator * a été modifiée pour clarifier cela, rendant l’implémentation de libstdc ++ incorrecte. La norme dit maintenant:
[Remarque: cette opération doit utiliser une variable membre auxiliaire plutôt qu’une variable temporaire pour éviter de renvoyer une référence qui persiste au-delà de la durée de vie de l’iterator associé. (Voir 24.2.) – append une note]
commentaire de Jonathan Wakely (2012)
Donc, cela ressemble à un bug … mais à la fin du sujet:
La définition de reverse_iterator est revenue à la version C ++ 03, qui n’utilise pas de membre supplémentaire, de sorte que les “iterators itinérants” ne peuvent pas être utilisés avec reverse_iterator.
commentaire de Jonathan Wakely (2014)
Il semble donc que l’utilisation de std::reverse_iterator
avec des “iterators” cache bien UB.
En regardant le DR 2204: ” reverse_iterator
ne devrait pas nécessiter une deuxième copie de l’iterator de base” , clarifie davantage le problème:
Cette note dans 24.5.1.3.4 [reverse.iter.op.star] / 2:
[Remarque: cette opération doit utiliser une variable membre auxiliaire plutôt qu’une variable temporaire pour éviter de renvoyer une référence qui persiste au-delà de la durée de vie de l’iterator associé. (Voir 24.2.) – append une note]
[ma note: je pense que la note ci-dessus résoudrait votre problème avec UB]
est incorrect car ces implémentations d’iterators sont exclues par 24.2.5 [forward.iterators] / 6, où il est dit:
Si a et b sont tous deux déréférencables, alors un == b si et seulement si * a et * b sont liés au même object.