簡體   English   中英

C++ 范圍適配器/視圖和迭代器失效規則

[英]C++ Range-adaptors/views and iterator invalidation rules

在修改底層容器時,我沒有發現任何對范圍/范圍適配器/范圍視圖特定失效規則的直接引用

直覺表明它與指針/迭代器失效規則完全相同——它們在標准的容器部分中指定。

目前容器失效的寫法如下:

“...使引用序列中元素的所有引用、指針和迭代器以及尾后迭代器無效。”

這就提出了一個問題:是否所有范圍都必須“引用序列的元素”,或者它們是否可以通過容器的接口訪問元素?

在我看來,大多數范圍適配器已經訪問一個序列而不直接引用該序列的元素(即惰性視圖只是構建迭代器適配器)。

可以說,重要的是視圖金字塔底部的基礎范圍。


我們都在某個時候了解到,您不能在迭代同一向量時執行std::vector::push_back ,因為內存可能會移動並使迭代無效。 但是,我們還了解到,您可以std::vector::operator[]訪問與 push_back 一起使用,只要您小心地正確檢查size()邊界即可。

在我看來,相同的規則適用於范圍/適配器/視圖。

所以:是否有可能強制一些等同於std::ranges::views::all (或者,也許take_view )的隨機訪問容器使用數組索引(或一些等效的間接/惰性元素訪問),而不使用直接迭代?


允許這樣做的東西:

std::vector<People> people = ...;
for (auto& person : std::ranges::views::lazy_all(people)) { // or ranges::lazy_take_view(people, people.size())
  if (person.has_new_child()) {
    people.push_back(person.get_new_child());
  }
}

我目前正在玩 C++20 范圍,在實現自己的視圖時,我想到了同樣的問題:視圖的迭代器失效規則是什么?

據我所知,范圍大量使用元編程,並且在底層它們構建了狀態機的層次結構。 這些狀態機的實際類型通常是隱藏的[1] ,因此我們可能難以假設它們的限制。 迭代器失效是這些限制的一部分,因此在我們構造視圖層次結構時指定迭代器何時以及如何失效即使不是不可能也是非常具有挑戰性的。 即使我們設法描述這些規則,也可能無法記住它們,更不用說有效地使用它們了。

Ranges V3 庫有以下推薦:

查看有效期

任何對其迭代器或哨兵無效的基礎范圍操作也會使引用該范圍任何部分的任何視圖無效。 此外,當范圍的基礎元素發生變化時,一些視圖(例如,views::filter)將失效。 最好在任何可能改變基礎范圍的操作之后重新創建視圖。

https://github.com/ericniebler/range-v3/blob/master/doc/index.md

上面給出的限制只是消除了所有顧慮,盡管它比標准容器的規則更嚴格,但它建立了一個簡單的規則來記住任何視圖迭代器的失效。 同時,它提供了在不觸及該規則的情況下更改視圖實現的自由。

所以,我想,可以安全地假設 C++20 標准中的范圍受到相同的限制。

[1] 我的觀察基於范圍的 MSVC 實現,其中范圍適配器實際上可以根據策略生成不同的類型。 因此,例如,當您對 std::views::take() 進行流水線處理時,您可能突然以 std::span() 結束。

每個范圍適配器或視圖對於如何與基礎范圍交互都有自己的規則。 該標准詳細說明了這些相互作用,即使有時是間接的。

例如, ranges::ref_view明確聲明可以像持有指向 range 的指針一樣工作 它的begin/end函數的行為就好像它們調用該范圍的begin/end函數一樣,以及將任何其他功能轉發到給定的范圍。 所以它的交互非常清楚,因為它的迭代器與底層范圍的迭代器是完全相同的類型。

對於像ranges::filter_view這樣的范圍,追蹤起來有點困難。 filter_view的迭代器行為基於filter_iterator的行為 該類型的行為就好像它將迭代器存儲到基礎范圍(由於僅用於說明的迭代器成員)。 因此,只要基礎范圍的迭代器失效, filter_iterator就會失效。 並且沒有filter_view的 exposition 成員持有迭代器,因此您可能期望調用begin總是會得到一個新鮮的、有效的filter_iterator

但它不會 filter_view::begin的描述中有一個重要的警告。 范圍的begin函數行為的語義組件是它必須在攤銷的常數時間內執行。 查找過濾列表的開頭是線性時間操作。 因此, filter_view::begin只需要執行一次這種線性時間操作,然后在內部緩存結果。

這意味着它確實存儲了一個迭代器,即使不清楚它是否存儲。 因此,如果您使 begin filter_iterator正在使用的任何內容無效,您就使filter_range的 begin 迭代器無效並且必須構造一個新迭代器。

總之,如果您想了解一個視圖的迭代器失效行為,您必須通讀該視圖的整個描述。 但這也適用於容器。 標准中沒有一個很好、簡潔的部分來准確說明迭代器、引用和指針何時失效。 Cppreference 有一個很好的列表,但標准將其留給類中每個函數的定義。

暫無
暫無

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

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