web-dev-qa-db-ja.com

標準のイテレータ範囲が[begin、end]ではなく[begin、end)なのはなぜですか?

なぜ標準はend()を実際の終わりではなく、終わりを過ぎたものとして定義するのですか?

198
Puppy

最も簡単な引数は、 Dijkstra自身 によって作成されたものです。

  • 範囲のサイズを単純な差にしたいendbegin;

  • シーケンスを空のシーケンスに縮退させる場合、下限を含めることはより自然です。また、代替(exclude下限)の存在が必要になるためです。 「開始前の」センチネル値。

それでも、1ではなく0でカウントを開始する理由を正当化する必要がありますが、それは質問の一部ではありませんでした。

[begin、end)規則の背後にある知恵は、自然に連鎖する範囲ベースの構造への複数のネストされた呼び出しまたは反復された呼び出しを処理する任意の種類のアルゴリズムがある場合、何度も報われます。対照的に、二重に閉じた範囲を使用すると、1つずつずれ、非常に不快でノイズの多いコードが発生します。たとえば、パーティション[nを考えますn1)[n1n2)[n2n3)。別の例は、_end - begin_回実行される標準の反復ループfor (it = begin; it != end; ++it)です。両端が含まれている場合、対応するコードははるかに読みにくくなります。空の範囲をどのように処理するかを想像してください。

最後に、なぜカウントをゼロから開始するのかという素敵な引数を作成することもできます:範囲を指定した場合[〜#〜] n [〜#〜]要素(たとえば、配列のメンバーを列挙する場合)、0は自然な「始まり」なので、範囲を[0、[〜#〜] n [〜#〜])、厄介なオフセットや修正なし。

簡単に言うと、範囲ベースのアルゴリズムのどこにも番号_1_が表示されないという事実は、[begin、end)規則の直接的な結果であり、その動機付けです。

281
Kerrek SB

実際、イテレータがシーケンスの要素をatを指していないがbetween、次の要素へのアクセスを間接参照します。次に、「ワンエンドエンド」イテレータが突然意味をなします。

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^               ^
   |               |
 begin            end

明らかにbeginはシーケンスの始まりを指し、endは同じシーケンスの終わりを指します。 beginを逆参照すると、要素Aにアクセスします。また、endを逆参照することは、適切な要素がないため意味がありません。また、中間にイテレータiを追加すると、

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
 begin     i      end

そしてすぐに、beginからiの要素の範囲にはAおよびBの要素が含まれ、iからendの要素の範囲にはCおよびDの要素が含まれます。 iを逆参照すると、その要素、つまり2番目のシーケンスの最初の要素が与えられます。

逆イテレータの「off-by-one」でさえ、そのように突然明らかになります。その順序を逆にすると、次のようになります。

   +---+---+---+---+
   | D | C | B | A |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
rbegin     ri     rend
 (end)    (i)   (begin)

対応する非逆(ベース)イテレータを以下の括弧内に記述しました。ご覧のとおり、i(私はriと名付けました)stillに属する逆反復子は、要素BCの間にあります。ただし、シーケンスが逆になっているため、要素Bはその右側にあります。

77
celtschk

なぜ標準はend()を実際の終わりではなく、終わりを過ぎたものとして定義するのですか?

なぜなら:

  1. 空の範囲に対する特別な処理を回避します。空の範囲の場合、begin()end()と等しくなります&
  2. これにより、要素を反復するループの終了基準が簡単になります。ループは、end()に到達しない限り単純に継続します。
72
Alok Save

なぜなら

size() == end() - begin()   // For iterators for whom subtraction is valid

する必要はありませんawkward

// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }

誤って間違ったコードを書くことはありません

bool empty() { return begin() == end() - 1; }    // a typo from the first version
                                                 // of this post
                                                 // (see, it really is confusing)

bool empty() { return end() - begin() == -1; }   // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators

また:find()が有効な要素を指していた場合、end()は何を返しますか?
reallyが必要ですかanother無効なイテレータを返すinvalid()と呼ばれるメンバー?!
2人のイテレーターは既に十分に苦痛です...

ああ、this 関連する投稿を参照してください


また:

endが最後の要素の前にある場合、どのようにinsert()を真の最後に配置しますか?

61
Mehrdad

半分閉じた範囲[begin(), end())のイテレータイディオムは、もともとプレーン配列のポインター演算に基づいています。その操作モードでは、配列とサイズが渡された関数があります。

_void func(int* array, size_t size)
_

その情報があれば、半閉鎖範囲_[begin, end)_への変換は非常に簡単です。

_int* begin;
int* end = array + size;

for (int* it = begin; it < end; ++it) { ... }
_

完全に閉じた範囲で作業するには、より困難です:

_int* begin;
int* end = array + size - 1;

for (int* it = begin; it <= end; ++it) { ... }
_

配列へのポインタはC++のイテレータであるため(および構文はこれを許可するように設計されています)、std::find(array, array + size, some_value)を呼び出すよりもstd::find(array, array + size - 1, some_value)を呼び出す方がはるかに簡単です。


さらに、半閉じた範囲で作業する場合、(演算子が正しく定義されている場合)_!=_は_<_を意味するため、_!=_演算子を使用して終了条件を確認できます。

_for (int* it = begin; it != end; ++ it) { ... }
_

ただし、完全に閉じた範囲でこれを行う簡単な方法はありません。あなたは_<=_で立ち往生しています。

C++で_<_および_>_操作をサポートする唯一の種類の反復子は、ランダムアクセス反復子です。 C++のすべてのイテレータクラスに対して_<=_演算子を記述する必要がある場合、すべてのイテレータを完全に比較可能にする必要があり、機能の低いイテレータ(双方向イテレータなど)を作成するための選択肢が少なくなります_std::list_、またはC++が完全に閉じた範囲を使用した場合は、iostreamsで動作する入力反復子。

22
Ken Bloom

end()が末尾を過ぎているので、forループを使用してコレクションを簡単に反復できます。

_for (iterator it = collection.begin(); it != collection.end(); it++)
{
    DoStuff(*it);
}
_

end()が最後の要素を指している場合、ループはより複雑になります。

_iterator it = collection.begin();
while (!collection.empty())
{
    DoStuff(*it);

    if (it == collection.end())
        break;

    it++;
}
_
8
Anders Abel
  1. コンテナが空の場合、begin() == end()
  2. C++プログラマーは、ループ条件で_!=_(より小さい)の代わりに_<_を使用する傾向があるため、end()が1つ後ろの位置を指していると便利です。
0
Andreas DM