[英]Keeping std::list iterators valid through insertion
Note: This is not a question whether I should "use list or deque".注意:这不是我应该“使用列表还是双端队列”的问题。 It's a question about the validity of iterators in the face of
insert()
.面对
insert()
,这是一个关于迭代器有效性的问题。
This may be a simple question and I'm just too dense to see the right way to do this.这可能是一个简单的问题,我太密集了,看不到正确的方法。 I'm implementing (for better or worse) a network traffic buffer as a
std::list<char> buf
, and I'm maintaining my current read position as an iterator readpos
.我正在实现(无论好坏)网络流量缓冲区作为
std::list<char> buf
,并且我将当前读取的 position 维护为迭代器readpos
。
When I add data, I do something like当我添加数据时,我会做类似的事情
buf.insert(buf.end(), newdata.begin(), newdata.end());
My question is now, how do I keep the readpos
iterator valid?我现在的问题是,如何保持
readpos
迭代器有效? If it points to the middle of the old buf
, then it should be fine (by the iterator guarantees for std::list), but typically I may have read and processed all data and I have readpos == buf.end()
.如果它指向旧
buf
的中间,那么它应该没问题(通过 std::list 的迭代器保证),但通常我可能已经读取并处理了所有数据并且我有readpos == buf.end()
。 After the insertion, I want readpos
always to point to the next unread character, which in case of the insertion should be the first inserted one.插入后,我希望
readpos
始终指向下一个未读字符,在插入的情况下应该是第一个插入的字符。
Any suggestions?有什么建议么? (Short of changing the buffer to a
std::deque<char>
, which appears to be much better suited to the task, as suggested below.) (没有将缓冲区更改为
std::deque<char>
,这似乎更适合该任务,如下所示。)
Update: From a quick test with GCC4.4 I observe that deque and list behave differently with respect to readpos = buf.end()
: After inserting at the end, readpos is broken in a list, but points to the next element in a deque.更新:通过 GCC4.4 的快速测试,我观察到 deque 和 list 在
readpos = buf.end()
方面的行为不同:在最后插入后, readpos 在列表中被破坏,但指向 a 中的下一个元素双端队列。 Is this a standard guarantee?这是标准保证吗?
(According to cplusplus , any deque::insert() invalidated all iterators. That's no good. Maybe using a counter is better than an iterator to track a position in a deque?) (根据cplusplus ,任何 deque::insert() 都使所有迭代器无效。这不好。也许使用计数器比迭代器更好地跟踪双端队列中的 position?)
if (readpos == buf.begin())
{
buf.insert(buf.end(), newdata.begin(), newdata.end());
readpos = buf.begin();
}
else
{
--readpos;
buf.insert(buf.end(), newdata.begin(), newdata.end());
++readpos;
}
Not elegant, but it should work.不优雅,但它应该工作。
From http://www.sgi.com/tech/stl/List.html来自http://www.sgi.com/tech/stl/List.html
"Lists have the important property that insertion and splicing do not invalidate iterators to list elements, and that even removal invalidates only the iterators that point to the elements that are removed." “列表具有重要的属性,即插入和拼接不会使列表元素的迭代器无效,即使删除也只会使指向被删除元素的迭代器无效。”
Therefore, readpos
should still be valid after the insert.因此,
readpos
在插入后应该仍然有效。
However...然而...
std::list< char >
is a very inefficient way to solve this problem. std::list< char >
是解决此问题的一种非常低效的方法。 Each byte you store in a std::list
requires a pointer to keep track of the byte, plus the size of the list node structure, two more pointers usually.您存储在
std::list
中的每个字节都需要一个指针来跟踪字节,加上列表节点结构的大小,通常还有两个指针。 That is at least 12 or 24 bytes (32 or 64-bit) of memory used to keep track of a single byte of data.也就是说,至少有 12 或 24 个字节(32 或 64 位)的 memory 用于跟踪单个数据字节。
std::deque< char>
is probably a better container for this. std::deque< char>
可能是一个更好的容器。 Like std::vector
it provides constant time insertions at the back however it also provides constant time removal at the front.像
std::vector
一样,它在后面提供恒定时间插入,但它也在前面提供恒定时间删除。 Finally, like std::vector
std::deque
is a random-access container so you can use offsets/indexes instead of iterators.最后,像
std::vector
std::deque
是一个随机访问容器,因此您可以使用偏移量/索引而不是迭代器。 These three features make it an efficient choice.这三个特点使它成为一个有效的选择。
I was indeed being dense.我确实很密集。 The standard gives us all the tools we need.
该标准为我们提供了我们需要的所有工具。 Specifically, the sequence container requirements 23.2.3/9 say:
具体来说,序列容器要求 23.2.3/9 说:
The iterator returned from
a.insert(p, i, j)
points to the copy of the first element inserted intoa
, orp
ifi == j
.从
a.insert(p, i, j)
返回的迭代器指向插入到a
中的第一个元素的副本,如果i == j
则指向p
。
Next, the description of list::insert
says (23.3.5.4/1):接下来,
list::insert
的描述说 (23.3.5.4/1):
Does not affect the validity of iterators and references.
不影响迭代器和引用的有效性。
So in fact if pos
is my current iterator inside the list which is being consumed, I can say:所以事实上,如果
pos
是我当前正在使用的列表中的迭代器,我可以说:
auto it = buf.insert(buf.end(), newdata.begin(), newdata.end());
if (pos == buf.end()) { pos = it; }
The range of new elements in my list is [it, buf.end())
, and the range of yet unprocessed elements is [pos, buf.end())
.我的列表中新元素的范围是
[it, buf.end())
,尚未处理的元素的范围是[pos, buf.end())
。 This works because if pos
was equal to buf.end()
before the insertion, then it still is after the insertion, since insertion does not invalidate any iterators, not even the end.这是因为如果
pos
在插入之前等于buf.end()
,那么它仍然在插入之后,因为插入不会使任何迭代器失效,甚至不会使结尾失效。
list<char>
is a very inefficient way to store a string. list<char>
是一种非常低效的存储字符串的方法。 It is probably 10-20 times larger than the string itself, plus you are chasing a pointer for every character...它可能比字符串本身大 10-20 倍,而且您正在为每个字符追逐一个指针......
Have you considered using std::dequeue<char>
instead?您是否考虑过使用
std::dequeue<char>
代替?
[edit] [编辑]
To answer your actual question, adding and removing elements does not invalidate iterators in a list
... But end()
is still going to be end()
.要回答您的实际问题,添加和删除元素不会使
list
中的迭代器无效......但是end()
仍然是end()
。 So you would need to check for that as a special case at the point where you insert the new element in order to update your readpos
iterator.因此,您需要在插入新元素以更新您的
readpos
迭代器时将其作为特殊情况进行检查。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.