web-dev-qa-db-ja.com

C ++イテレーター:範囲の終わりを表すためのベストプラクティス-最後かそれとも最後か?

注文したコンテナのサブシーケンスを扱うライブラリを書いています。

たとえば、コンテナ(1,2,3,4,5,6)があり、ユーザーがアクセスしたい(3,4,5)とします。

最初の要素とlast要素をそれぞれ指す、つまり3と5のイテレータのペアによってサブシーケンスを提供しています。

ライブラリはC++とAFAIKで記述されているため、stdの規則では最後のイテレータポイントbeyondが最後の要素になるようになっています。イテレータ、最初と最後を超えて要素をそれぞれ指す、つまり3と6?


また、プログラミングの観点からは、std機能を使用する場合、たとえば要素の数を数えるために物事を複雑にします。

int elementCnt = std::distance(startIt, endIt) + 1;
6
1v0

標準に従ってください-終わりはあなたが望むものを過ぎたイテレータです。これにより、すべての標準アルゴリズムとコンテナを問題なく使用できます。

また、ユーザーが常に持っているコードを記述できるようになります(例:for (x=startIt; x != endIt; x++))。これは期待どおりに機能します。

この動作を変更して、最後のイテレーターを最後の要素に設定すると、すべてがウィンドウの外に出て、イテレーターとは異なる命名法を使用して、全員が期待する動作を効果的に変更することができます。

35
gbjbaanb

あなたの慣習で:

  • algorithm ライブラリーのすべての関数は、範囲の上限を変更して使用する必要があり、エラーが発生しやすくなります。
  • 空のシーケンスを表すのは簡単ではありません(これは 番号付けがゼロから始まる理由 のダイクストラの引数でした)。
  • 1つ前のエラー(コレクションのパーティションを作成するときなど)が発生する可能性があります。

半分閉じた範囲を維持する必要があります。

9
manlio

あなたが書いた:

Stdの規則では、最後の要素を超えて最後の反復子ポイントを使用します

私はあなたに2つの小さな返信(それぞれ1つのセクション)を与えることによってあなたのメンタルモデルを助けることができると思います。

  • それを最後のインデックス付けとは考えないでください、エッジベースのインデックス付けと考えてください
  • エッジベースのインデックス付け(右開きのインターバルインデックス付け)が優れている理由

それを最後のインデックス付けとは考えないでください、エッジベースのインデックス付けと考えてください

Snowman による非常に役立つコメントのおかげで、このセクションを大幅に簡略化してC++化しました。

C++イテレータは、「現在どの項目を指しているか」ではなく、「次に取得する項目」という用語で定義されています。

そのため、イテレータをアイテムではなくエッジの直前に置くと考えると役立ちます。

開始と停止のあるサブシーケンスの場合、アイテムに番号を付ける代わりに、アイテム間のエッジに精神的に番号を付けます。 0は最初のアイテムの前のエッジです。 startItは私が始めたエッジです。 stopItは、私が立ち寄るエッジです。

次の図は Pythonの非公式な紹介 からの抜粋です。

   item    0   1   2   3   4   5
         +---+---+---+---+---+---+
         | P | y | t | h | o | n |
         +---+---+---+---+---+---+
iterator 0   1   2   3   4   5   6

したがって、startIt = 2およびstopIt = 5t, h, oにつながります。

エッジベースのインデックス付け(右開きのインターバルインデックス付け)が優れている理由

あなたはいくつかの本当に素晴らしいプロパティを得ます:

  • サブシーケンス内のアイテム数:n = stop - start
  • 隣接するサブシーケンスを作成するには、stop of one == start of the next.

以下の例。 Python構文は以下の構文を使用しています。C++がわからないためです。誰かがこのセクションをC++に翻訳したいと思っている場合は(Pythonを離れる必要はありません)、非常に感謝します。)とにかく、表記は重要ではありません。[start:stop]startItstopItとして読んでください。

これが使用するコンテナです

my_container = [ 'a', 'b', 'c', 'd' ]
## edges       ^    ^    ^    ^     ^
##             0    1    2    3     4

c[start:stop]のようにスライスしてサブシーケンスにアクセスします-エッジ1と3の間のすべてを取得します。

my_container[1:3] == ['b', 'c']

長さ3のスライスを取得するには、stop=startを確認します+ 3

my_container[1:4] == ['b', 'c', 'd']
# or do stop - start to find out how long the slice is:
4 - 1 == 3  # 3 elements in this slice.

前のスライスが終わるところから1つのスライスを始めたい。つまり、最初のスライスはEdgexで終わり、2番目のスライスはEdgex。この方法で、コンテナーをきれいに2つに分割します。

my_container[0:3] == ['a', 'b', 'c']
my_container[3:4] == ['d']

閉会の辞

manlio's answer のEdsger W. Dijkstraによるエッセイを読んでください。 700ワード未満で、非常に明確な思考と同様に明確な手書き(および内部のHTMLバージョンへのリンク)を備えています。

5
Esteis

標準ライブラリは、正当な理由で、過去の終わりのポインタを使用します-このパターンに固執します。別の方法では、空の範囲を説明する良い方法はありません。

また、何か特別な/特別な/ ここに書かれたが絶対に必要でない限り、単に Boost.Range を使用してください。

0
Useless

プログラミング言語で使用されている規則(定義)を維持する必要があります。

例:

#include <iterator>

template< class Iterator>
class Range
{
    public:
    typedef typename std::iterator_traits<Iterator>::value_type value_type;
    typedef Iterator iterator;

    Range(const iterator& first, const iterator& last) noexcept
    :   m_first(first), m_last(last)
    {}

    Range(iterator&& first, iterator&& last) noexcept
    :   m_first(std::move(first)), m_last(std::move(last))
    {}

    Range(Range&& other) noexcept
    :   m_first(std::move(other.m_first)),
        m_last(std::move(other.m_last))
    {}

    Range& operator = (Range&& other) noexcept {
        m_first = std::move(other.m_first);
        m_last = std::move(other.m_last);
        return *this;
    }

    iterator begin() const noexcept { return m_first; }
    iterator end() const noexcept { return m_last; }

    private:
    iterator m_first;
    iterator m_last;
};

template<typename T>
inline Range<T> range(T&& first, T&& last) noexcept {
    return Range<T>(std::forward<T>(first), std::forward<T>(last));
}

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = { 1,2,3,4,5,6 };
    for(auto i : range(v.begin() + 1, v.end() - 1))
        std::cout << i << '\n';
    for(auto i : range(v.end(), v.end()))
        std::cout << i << '\n';
}

慣習に固執しないと、ライブラリ(アルゴリズム)や言語機能(範囲ベース)の使用が面倒になります。さらに悪いことに、一般的な慣習を期待しているプログラマが微妙なエラーにつながる可能性があります。また、[first、last]が含まれている場合、空の範囲を表す方法はありません。

0
Dieter Lücking