簡體   English   中英

哨兵和結束迭代器有什么區別?

[英]What's the difference between a sentinel and an end iterator?

在閱讀 Eric Niebler 的范圍建議時,
我遇到了哨兵一詞作為結束迭代器的替代品。
我很難理解哨兵相對於結束迭代器的好處。
有人可以提供一個清晰的例子來說明哨兵帶來的標准迭代器對無法完成的事情嗎?

哨兵是結束迭代器的抽象。哨兵是常規類型,可用於表示范圍的結束。哨兵和表示范圍的迭代器應為 EqualityComparable。哨兵表示一個元素,當迭代器 i 比較等於哨兵,並且 i 指向那個元素。” -- N4382

我認為哨兵作為確定范圍結束的功能,而不僅僅是位置?

Sentinel 只是允許結束迭代器具有不同的類型。

過去迭代器上允許的操作是有限的,但這並沒有反映在它的類型中。 * .end()迭代器是不行的,但是編譯器會讓你。

哨兵沒有一元解引用或++等。 它通常與結束迭代器之后的最弱迭代器一樣受到限制,但在編譯時強制執行。

有回報。 通常檢測最終狀態比找到它更容易。 使用哨兵, ==可以在編譯時而不是運行時分派“檢測另一個參數是否超過結尾”。

結果是一些過去比 C 等效代碼慢的代碼現在編譯到 C 級別的速度,例如使用std::copy復制一個以空結尾的字符串。 如果沒有哨兵,您要么必須掃描以在副本之前找到結尾,要么傳入帶有布爾標志的迭代器,上面寫着“我是終點哨兵”(或等效),然后在==上檢查它。

使用基於計數的范圍時還有其他類似的優點。 此外,諸如 zip 范圍1之類的東西變得更容易表達(末端 zip 哨兵可以同時保存兩個源哨兵,並且如果其中一個哨兵存在則返回相等:zip 迭代器要么只比較第一個迭代器,要么比較兩者)。

另一種思考方式是算法傾向於不使用迭代器概念的完整豐富性來傳遞作為結束迭代器的參數,並且迭代器在實踐中的處理方式不同。 哨兵意味着調用者可以利用這個事實,這反過來又讓編譯器更容易利用它。


1一個 zip range 是當您從 2 個或多個 range 開始時得到的,然后像拉鏈一樣將它們“壓縮”在一起。 該范圍現在位於各個范圍元素的元組之上。 推進一個 zip 迭代器推進每個“包含”的迭代器,同樣用於解引用和比較。

哨兵和結束迭代器的相似之處在於它們標記了范圍的結束。 它們的不同之處在於如何檢測到這一端。 您正在測試迭代器本身,或者您正在測試迭代器的數據值。 如果您已經在對數據執行測試,則哨兵可以讓您的算法“免費”完成,而無需任何額外的測試。 這可以簡化代碼,或者使其更快。

一個非常常見的標記是用於標記字符串結尾的零字節。 不需要為字符串的末尾保留一個單獨的迭代器,它可以在您使用字符串本身的字符時確定。 此約定的缺點是字符串不能包含零字符。

請注意,我在閱讀鏈接中的提案之前寫了這個答案; 這是哨兵的經典定義,可能與那里提出的定義不一致。

引入哨兵的主要動機是有很多迭代器操作得到支持,但通常對於 end-iterator end()來說是不需要的。 例如,通過*end()取消引用它,通過++end()遞增它等等 (*) 幾乎沒有任何意義。

相比之下, end()的主要用途只是將它與迭代器it進行比較,以表明it是否位於它剛剛迭代的事物的末尾。 而且,像往常一樣在編程中,不同的要求和不同的應用程序提出了一種新的類型。

range-v3 庫將此觀察結果轉化為假設(通過概念實現):它為end()引入了一種新類型,並且只要求它與相應的迭代器相等可比——但不需要通常的迭代器操作)。 這種新類型的end()稱為sentinel

這里的主要優勢是獲得的抽象和更好的關注點分離,基於此編譯器可能能夠執行更好的優化。 在代碼中,基本思想是這樣的(這只是為了解釋,與 range-v3 庫無關):

struct my_iterator;    //some iterator
struct my_sentinel
{
     bool is_at_end(my_iterator it) const
     {
         //here implement the logic when the iterator is at the end
     }
};

auto operator==(my_iterator it, my_sentinel s)  //also for (my_sentinel s, my_iterator it)
{
    return s.is_at_end(it); 
}

看到抽象了嗎? 現在,您可以在is_at_end函數中實現您想要的任何檢查,例如:

  • 永不停止(獲得無限范圍)
  • N增量后停止(以獲得計數范圍)
  • 遇到\0時停止,即*it = '\0' (用於循環 C 字符串)
  • 到 12 點時停止(吃午飯),依此類推。

此外,關於性能,可以在檢查中使用編譯時間信息(例如,將上面的N視為編譯時間參數)。 在這種情況下,編譯器可能能夠更好地優化代碼。


(*) 請注意,這並不意味着這種操作通常沒有用處。 例如, --end()在某些地方可能很有用,請參見例如這個問題 然而,似乎可以在沒有這些的情況下實現標准庫——這就是 range-v3 庫所做的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM