web-dev-qa-db-ja.com

イテレータが最後のアイテムを指しているかどうかをテストしていますか?

Std :: find()の結果であるstlイテレータがあり、それが最後の要素であるかどうかをテストしたいと思います。これを記述する1つの方法は次のとおりです。

mine *match = someValue;
vector<mine *> Mine(someContent);
vector<mine *>::iterator itr = std::find(Mine.begin(), Mine.end(), match);

if (itr == --Mine.end()) {
  doSomething;
}

しかし、end()イテレータをデクリメントすると、ベクトルに要素がない場合など、問題が発生する可能性があるように見えます。空にならないことがわかっていても、醜いようです。おそらくrbegin()が良い方法だと思いますが、順方向反復子と逆方向反復子を比較するための最良の方法については定かではありません。

30
WilliamKF

これを行う:

_// defined in boost/utility.hpp, by the way
template <typename Iter>
Iter next(Iter iter)
{
    return ++iter;
}

// first check we aren't going to kill ourselves
// then check if the iterator after itr is the end
if ((itr != Mine.end()) && (next(itr) == Mine.end()))
{
    // points at the last element
}
_

以上です。未定義の動作を決して与えず、すべてのイテレータで動作します。

楽しみのためにまとめてください:

_template <typename Iter, typename Cont>
bool is_last(Iter iter, const Cont& cont)
{
    return (iter != cont.end()) && (next(iter) == cont.end())
}
_

与える:

_if (is_last(itr, Mine))
_

ユーティリティ関数/見栄えのよいコードにアレルギーがある場合は、次のようにします。

_if ((itr != Mine.end()) && (itr + 1 == Mine.end()))
_

しかし、ランダムアクセス以外の反復子では実行できません。これは双方向イテレータで動作します:

_if ((itr != Mine.end()) && (itr == --Mine.end()))
_

そして最初のチェックでend() > itr以来安全です。

56
GManNickG

はい、ベクターが空の場合、endをデクリメント(またはインクリメント)することは安全ではありません。ポインタを使って同じことをするのは少し安全ではありませんが、おそらくそれでうまくいくでしょう。

本当に安全であるためには、安全で有効であることがわかっている減算と値を使用します。

if ( Mine.end() - itr == 1 )

すべての順方向反復子との互換性(slistvectorのランダムアクセス反復子ではなく、deque内など)を使用するには

if ( std::distance( itr, Mine.end() ) == 1 )

または、パフォーマンスに関心があるが、双方向イテレータ(C++ 03コンテナを含む)がある場合

if ( itr != Mine.end() && itr == -- Mine.end() )

または、フォワードイテレータとO(1)時間のみの本当に肛門の場合

if ( itr != Mine.end() && ++ container::iterator( itr ) == Mine.end() )

または、イテレータクラスの名前を付けないように賢明さを求めている場合は、

if ( itr != Mine.end() && ++ ( Mine.begin() = itr ) == Mine.end() )
11
Potatoswatter

アイテムが最後のものである場合にのみ特別な動作を行う必要があるのはなぜですか?

これはどうですか。計画は、イテレータのアイテムのアドレスをコンテナ内の最後のアイテムのアドレスと比較し、アイテムが実際にまだ終了していないことを確認する(back呼び出しを安全にする)ことです。

if (itr != Mine.end() && &*itr == &Mine.back()) {
  doSomething;
}
5
Mark B

最初に イテレータが逆のものであるかどうかを判断する方法が必要です これは ここで熱心に示されています

_#include <iterator>
#include <type_traits>

template<typename Iter>
struct is_reverse_iterator : std::false_type { };

template<typename Iter>
struct is_reverse_iterator<std::reverse_iterator<Iter>>
: std::integral_constant<bool, !is_reverse_iterator<Iter>::value>
{ };
_

次に、テストを実行するために2つのフレーバーを持つことができます

_template<bool isRev> // for normal iterators
struct is_last_it
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .end()
        return it != cont.end() && ++it == cont.end();
    }
};

template<> // for reverse iterators
struct is_last_it<true>
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .rend()
        return it != cont.rend() && ++it == cont.rend();
    }
};
_

そして、単一のインターフェース機能

_template<typename It, typename Cont>
bool is_last_iterator(It it, Cont const &cont)
{
    return is_last_it<is_reverse_iterator<It>::value>::apply(it, cont);
};
_

次に、すべてのタイプのイテレータ(リバース/ストレート)に対して、インターフェース関数を使用できます

_int main()
{
    std::vector<int> v;
    v.Push_back(1);

    auto it (v.begin()),  ite(v.end());   // normal iterators
    auto rit(v.rbegin()), rite(v.rend()); // reverse iterators

    std::cout << is_last_iterator(it, v) << std::endl;
    std::cout << is_last_iterator(ite, v) << std::endl;
    std::cout << is_last_iterator(rit, v) << std::endl;
    std::cout << is_last_iterator(rite, v) << std::endl;

    return 0;
}
_

一部の実装(十分に一般的なstd::begin()およびstd::end()は別として、std::rbegin()およびstd::rend()も含まれています。可能な場合、このセットを使用してください。メンバの代わりに関数の[.begin()など.

4

もしあなたがそうするなら:

if(itr != Mine.end() && itr == --Mine.end())

それは大丈夫です。 itrが最後にない場合、コンテナには少なくとも1つの要素が必要であり、そのため、デクリメントするとendが値の結果を生成する必要があるためです。

しかし、それでも気に入らない場合は、他のすべての回答が示すように、同等のことを行う方法はたくさんあります。

ここに別の選択肢があります:

if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)
3
TheUndeadFish

次に、別の解決策を示します。

template<class Iterator, class Container> bool is_last(Iterator it, const Container& cont)
{
    // REQUIREMENTS:
    // the iterator must be a valid iterator for `cont`
    if( it == cont.end() )
        return false;   // or throw if you prefer
    return (++it) == cont.end();
}
2
John Dibling

この答えをできるだけシンプルで用途の広いものにしようとしています:

if( itr!=Mine.end() && itr== --Mine.end())

イテレータが双方向でない場合、

if( itr!=Min.end() && ++decltype(itr)(itr)==Mine.end())

2つ目は、itrの一時的なコピーを作成し、それをインクリメントして、終了イテレータに対してテストします。

どちらの場合でも、最初のテストでは空のコンテナを避けて、未定義の状況を引き起こします。

1
Adrian Maire

より良い方法は、イテレータをコピーしてからそれをインクリメントすることです。その後、インクリメントされたバージョンをend()に対してテストできます。注意する場合は、ポストインクリメントを使用して、正式にコピーする必要を回避できます。

  if (++vector<mine*>::iterator(itr) == Mine.end())

Itrがすでに最後にある場合:

  if (itr == Mine.end() || ++vector<mine*>::iterator(itr) == Mine.end())

または、GManの回答に基づいていますが、少し安全です。

  if (Mine.Length() == 0 || itr == Mine.End() || &*itr == &Mine.back())

&*に問題があったため、最後の問題をもう一度修正しました。

1
Steven Sudit

これは基本的に、単一リンクリストからノードを削除するのと同じ問題です。 2つのイテレータが必要です。1つは他のノードの後に​​続きます。そのため、「フォワード」イテレータが削除するノード(または任意の操作。目的のノードの場合は最後)に到達すると、「 「次の」イテレータは前のノードを指しています(あなたの場合、これは最後のノードです)。

1
rmeador