简体   繁体   English

通过插入保持 std::list 迭代器有效

[英]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 into a , or p if i == 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM