数値のような型をとるAsIterator
テンプレートクラスがあります。この例では、int
だけで、それをイテレータ(++
および--
インクリメント)に変換します。数値をデクリメントすると、operator*
はその数値への参照を返すだけです)。
これは正常に機能しますstd::reverse_iterator
にラップされ、最適化(-O
で十分)でコンパイルされていない限り。バイナリを最適化すると、コンパイラはreverse_iterator
への逆参照呼び出しを取り除き、それを奇妙な値に置き換えます。それでも正しい反復回数を行うであることに注意する必要があります。ガベージであるのは、逆イテレータによって取得された値だけです。
次のコードについて考えてみます。
#include <iterator>
#include <cstdio>
template<typename T>
class AsIterator : public std::iterator<std::bidirectional_iterator_tag, T> {
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<int>> 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<int> real_begin(a);
AsIterator<int> real_end(b);
for (ReverseIt rev_it(real_end); rev_it != ReverseIt(real_begin); ++rev_it) {
printf("%d\n", *rev_it);
}
return 0;
}
これはおそらく、挿入された最大の数値から最小の数値にループダウンして、この実行のようにそれらを出力する必要があります(-O0
でコンパイル):
Insert two integers: 1 4
3
2
1
代わりに、-O
で得られるものは次のとおりです。
Insert two integers: 1 4
1
0
0
あなたができる ここでオンラインで試してみてください ;数値は異なる場合がありますが、バイナリを最適化する場合は常に「間違っています」。
私が試したこと:
const
にする(つまり、const int &
を返し、すべての変数をそのように宣言する)と、それは修正されません。reverse_iterator
で同じようにstd::vector<int>
を使用すると、正常に機能します。AsIterator<int>
を使用するだけで、正常に機能します。0
は実際にはコンパイラによってハードコードであり、-S -O
でコンパイルすると、printf
の呼び出しはすべて次のようになります。 movl $.L.str.2, %edi # .L.str.2 is "%d\n"
xorl %eax, %eax
callq printf
ここでのclangとgccの動作の一貫性を考えると、彼らはそれを正しく行っていると確信しており、誤解しましたが、実際にはそれを見ることができません。
std::reverse_iterator
のlibstdc ++実装を見ると、興味深いことがわかります。
/**
* @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, @c x, a reference obtained by
* @c *x remains valid after @c x has been modified or
* destroyed. This is a bug: http://gcc.gnu.org/PR51823
*/
_GLIBCXX17_CONSTEXPR reference
operator*() const
{
_Iterator __tmp = current;
return *--__tmp;
}
@warning
セクションは、基礎となるイテレータータイプの要件は、基礎となるイテレーターが変更/破棄された後でも*x
が有効なままでなければならないことを示しています。
前述のバグリンク を見ると、より興味深い情報が明らかになります。
c ++ 03とC++ 11の間のある時点で、reverse_iterator :: operator *の定義が変更され、これが明確になり、libstdc ++の実装が正しくなくなりました。標準は今言う:
[注:この操作では、関連するイテレーターの存続期間を超えて存続する参照が返されないように、一時変数ではなく補助メンバー変数を使用する必要があります。 (24.2を参照)—エンドノート]
jonathan Wakelyによるコメント(2012)
だからそれはバグのように見えます...しかしトピックの終わりに:
Reverse_iteratorの定義は、追加のメンバーを使用しないC++ 03バージョンに戻されたため、「スタッシングイテレーター」をreverse_iteratorと一緒に使用することはできません。
jonathan Wakelyによるコメント(2014)
したがって、std::reverse_iterator
を「スタッシングイテレータ」とともに使用すると、実際にUBにつながるようです。
DR 2204: "reverse_iterator
はベースイテレータの2番目のコピーを必要としないはずです" を見るとさらに明確になります問題:
24.5.1.3.4 [reverse.iter.op.star]/2のこのメモ:
[注:この操作では、関連するイテレーターの存続期間を超えて存続する参照を返さないように、一時変数ではなく補助メンバー変数を使用する必要があります。 (24.2を参照)—文末脚注]
[私のメモ:上記のメモでUBの問題が解決すると思います]
このようなイテレータの実装は24.2.5 [forward.iterators]/6によって除外されているため、正しくありません。
aとbの両方が逆参照可能である場合、* aと* bが同じオブジェクトにバインドされている場合に限り、a == bです。