[英]Iterator invalidation by `std::string::begin()`/`std::string::end()`?
#include <string>
#include <iostream>
int main() {
std::string s = "abcdef";
std::string s2 = s;
auto begin = const_cast<std::string const &>(s2).begin();
auto end = s2.end();
std::cout << end - begin << '\n';
}
此代碼將begin() const
的結果與end()
的結果混合在一起。 這些函數都不允許使任何迭代器無效。 但是我很好奇是否要求end()
不使iterator變量begin
失效實際上意味着變量begin
可以用於end
。
考慮一下C ++ 98, std::string
copy-on-write實現; 非const begin()
和end()
函數會導致復制內部緩沖區,因為這些函數的結果可用於修改字符串。 所以begin
上述開出有效的既s
和s2
,而是使用非const的end()
構件使其不再有效為s2
,產生它的容器。
上面的代碼通過copy-on-write實現產生“意外”結果,例如libstdc ++。 libstdc ++ 生成另一個數字 ,而不是end - begin
與s2.size()
相同。
是否導致begin
不再是s2
有效迭代器,從中檢索它的容器,構成“使迭代器無效”? 如果你看一下迭代器的要求,它們在調用.end()
之后似乎都保留了這個迭代器,所以也許begin
仍然有資格作為一個有效的迭代器,因此沒有被無效?
上面的代碼在C ++ 98中定義得很好嗎? 在C ++ 11中,它禁止了寫時復制實現?
根據我自己對規范的簡要介紹,它看起來不明確,因此可能無法保證begin()
和end()
可以一起使用,即使沒有混合const和非const版本。
正如您所說,C ++ 11在這方面與早期版本不同。 在C ++ 11中沒有問題,因為所有允許寫入時復制的嘗試都被刪除了。 在pre-C ++ 11中,您的代碼會導致未定義的行為; 允許調用s2.end()
使現有的迭代器無效(並且在g ++中完成,也可能仍然如此)。
請注意,即使s2
不是副本,標准也會允許它使迭代器無效。 事實上,C ++ 98的CD甚至可以制作f( s.begin(), s.end() )
或s[i] == s[j]
未定義的行為。 這只是在最后一刻才實現,並進行了更正,以便只有第一次調用begin()
, end()
或[]
才能使迭代器無效。
代碼沒問題:當迭代器存在危險或者對元素的引用被保留時,幾乎需要一個CoW實現來取消共享。 也就是說,當你有一些東西訪問一個字符串中的元素並且它的副本冒險做同樣的事情,即使用迭代器或下標操作符時,它必須是非共享的。 它可以知道它的迭代器並根據需要更新它們。
當然,在並發系統中,如果沒有數據競爭,幾乎不可能完成所有這些,但是在C ++ 11之前沒有數據競爭。
從N3337開始( 與C ++ 11基本相同 ),規范讀取([string.require] / 4):
引用basic_string序列元素的引用,指針和迭代器可能會被該basic_string對象的以下用法無效:
[...]
- 調用非const成員函數,除了operator [],at,front,back,begin,rbegin,end和rend。
至少在我讀它時,這意味着不允許對begin
或end
的調用使任何迭代器無效。 雖然沒有直接說明,但我還是認為沒有調用const
成員函數可以使任何迭代器無效。
這個措辭至少在n4296之前保持不變。
C ++ 98 [lib.basic.string] / 5表示:
引用
basic_string
序列元素的引用,指針和迭代器可能會被basic_string
對象的以下用法無效:
作為非成員函數的參數
swap()
,operator>>()
和getline()
。作為
basic_string::swap()
的參數。調用
data()
和c_str()
成員函數。調用非const成員函數,除了
operator[]()
,at()
,begin()
,rbegin()
,end()
和rend()
。除了返回迭代器的
insert()
和erase()
形式之外的任何上述用法之后,第一次調用非const成員函數operator[]()
,at()
,begin()
,rbegin()
,end()
或rend()
。
由於s2
的構造函數是一個“非const成員函數”,因此它符合對非const s2.end()
的調用 - 這是上面最后一個項目符號的第一個調用 - 使迭代器無效。 因此,程序沒有按照C ++ 98定義的行為。
我不會評論C ++ 11,因為我認為其他答案清楚地解釋了程序在該上下文中定義了行為。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.